/*
 * (C) 2006-2025 see Authors.txt
 *
 * This file is part of MPC-BE.
 *
 * MPC-BE is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * MPC-BE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "stdafx.h"
#include <wmcodecdsp.h>
#include <moreuuids.h>
#include <dxva2_guids.h>
#include "MPCVideoDec.h"
#include "DXVADecoder/DXVAAllocator.h"
#include "FfmpegContext.h"
#include "./D3D11Decoder/D3D11Decoder.h"

#include "DSUtil/CPUInfo.h"
#include "DSUtil/D3D9Helper.h"
#include "DSUtil/DSUtil.h"
#include "DSUtil/ffmpeg_log.h"
#include "DSUtil/GolombBuffer.h"
#include "DSUtil/SysVersion.h"
#include "DSUtil/DXVAState.h"
#include "DSUtil/VideoParser.h"
#include "filters/parser/AviSplitter/AviSplitter.h"
#include "filters/parser/OggSplitter/OggSplitter.h"
#include "filters/parser/MpegSplitter/MpegSplitter.h"
#include "filters/parser/FLVSplitter/FLVSplitter.h"
#include "filters/Lock.h"
#include <FilterInterfaces.h>

#include "mvrInterfaces.h"

#include "DxgiUtils.h"

#pragma warning(push)
#pragma warning(disable: 4005)
#pragma warning(disable: 4996)
#pragma warning(disable: 5033)
extern "C" {
	#include <ExtLib/ffmpeg/libavcodec/avcodec.h>
	#include <ExtLib/ffmpeg/libavcodec/dxva2.h>
	#include <ExtLib/ffmpeg/libavutil/intreadwrite.h>
	#include <ExtLib/ffmpeg/libavutil/imgutils.h>
	#include <ExtLib/ffmpeg/libavutil/mastering_display_metadata.h>
	#include <ExtLib/ffmpeg/libavutil/dovi_meta.h>
	#include <ExtLib/ffmpeg/libavutil/opt.h>
	#include <ExtLib/ffmpeg/libavutil/hwcontext_cuda_internal.h>
	#include <ExtLib/ffmpeg/libavutil/hwcontext_d3d11va.h>
	#include <ExtLib/ffmpeg/libavutil/hwcontext_d3d12va.h>
}
#pragma warning(pop)

#include "Version.h"

// option names
#define OPT_REGKEY_VideoDec  L"Software\\MPC-BE Filters\\MPC Video Decoder"
#define OPT_SECTION_VideoDec L"Filters\\MPC Video Decoder"
#define OPT_ThreadNumber     L"ThreadNumber"
#define OPT_DiscardMode      L"DiscardMode"
#define OPT_ScanType         L"ScanType"
#define OPT_ARMode           L"ARMode"
#define OPT_HwDecoder        L"HwDecoder"
#define OPT_HwAdapter        L"HwAdapter"
#define OPT_DXVACheck        L"DXVACheckCompatibility"
#define OPT_DisableDXVA_SD   L"DisableDXVA_SD"
#define OPT_SW_prefix        L"Sw_"
#define OPT_SwConvertToRGB   L"SwConvertToRGB"
#define OPT_SwRGBLevels      L"SwRGBLevels"

static LPCWSTR hwdec_opt_names[] = {
	L"Hw_MPEG2",
	L"Hw_WMV3",
	L"Hw_VC1",
	L"Hw_H264",
	L"Hw_HEVC",
	L"Hw_VP9",
	L"Hw_AV1",
};

static_assert(std::size(hwdec_opt_names) == HWCodec_count, "bad hwdec_opt_names!");

#define MAX_AUTO_THREADS 32

#pragma region any_constants

#ifdef REGISTER_FILTER
#define OPT_REGKEY_VCodecs   L"Software\\MPC-BE Filters\\MPC Video Decoder\\Codecs"

static const struct vcodec_t {
	const LPCWSTR          opt_name;
	const unsigned __int64 flag;
}
vcodecs[] = {
	{L"h264",			CODEC_H264		},
	{L"h264_mvc",		CODEC_H264_MVC	},
	{L"mpeg1",			CODEC_MPEG1		},
	{L"mpeg3",			CODEC_MPEG2		},
	{L"vc1",			CODEC_VC1		},
	{L"msmpeg4",		CODEC_MSMPEG4	},
	{L"xvid",			CODEC_XVID		},
	{L"divx",			CODEC_DIVX		},
	{L"wmv",			CODEC_WMV		},
	{L"hevc",			CODEC_HEVC		},
	{L"vp356",			CODEC_VP356		},
	{L"vp89",			CODEC_VP89		},
	{L"theora",			CODEC_THEORA	},
	{L"mjpeg",			CODEC_MJPEG		},
	{L"dv",				CODEC_DV		},
	{L"lossless",		CODEC_LOSSLESS	},
	{L"prores",			CODEC_PRORES	},
	{L"canopus",		CODEC_CANOPUS	},
	{L"screc",			CODEC_SCREC		},
	{L"indeo",			CODEC_INDEO		},
	{L"h263",			CODEC_H263		},
	{L"shq",			CODEC_SHQ		},
	{L"svq3",			CODEC_SVQ3		},
	{L"realv",			CODEC_REALV		},
	{L"dirac",			CODEC_DIRAC		},
	{L"binkv",			CODEC_BINKV		},
	{L"amvv",			CODEC_AMVV		},
	{L"flash",			CODEC_FLASH		},
	{L"utvd",			CODEC_UTVD		},
	{L"png",			CODEC_PNG		},
	{L"uncompressed",	CODEC_UNCOMPRESSED},
	{L"dnxhd",			CODEC_DNXHD		},
	{L"cinepak",		CODEC_CINEPAK	},
	{L"quicktime",		CODEC_QT		},
	{L"cineform",		CODEC_CINEFORM	},
	{L"hap",			CODEC_HAP		},
	{L"av1",			CODEC_AV1		},
	{L"avs3",			CODEC_AVS3		},
	{L"vvc",			CODEC_VVC		},
};
#endif

struct FFMPEG_CODECS {
	const CLSID*         clsMinorType;
	const enum AVCodecID nFFCodec;

	const int            FFMPEGCode;
	const MPCHwCodec     HwCodec;
};

struct {
	const enum AVCodecID nCodecId;
	const GUID           decoderGUID;
	const bool           bHighBitdepth;
	const AVPixelFormat  pixFormat = AV_PIX_FMT_NONE;
} DXVAModes[] = {
	// H.264
	{ AV_CODEC_ID_H264, DXVA2_ModeH264_E, false },
	{ AV_CODEC_ID_H264, DXVA2_ModeH264_F, false },
	{ AV_CODEC_ID_H264, DXVA2_H264_VLD_Intel, false },
	// HEVC Rext
	{ AV_CODEC_ID_HEVC, D3D11_DECODER_PROFILE_HEVC_VLD_MAIN12,     false, AV_PIX_FMT_YUV420P12 },
	{ AV_CODEC_ID_HEVC, D3D11_DECODER_PROFILE_HEVC_VLD_MAIN10_422, false, AV_PIX_FMT_YUV422P10 },
	{ AV_CODEC_ID_HEVC, D3D11_DECODER_PROFILE_HEVC_VLD_MAIN12_422, false, AV_PIX_FMT_YUV422P12 },
	{ AV_CODEC_ID_HEVC, D3D11_DECODER_PROFILE_HEVC_VLD_MAIN_444,   false, AV_PIX_FMT_YUV444P   },
	{ AV_CODEC_ID_HEVC, D3D11_DECODER_PROFILE_HEVC_VLD_MAIN10_444, false, AV_PIX_FMT_YUV444P10 },
	{ AV_CODEC_ID_HEVC, D3D11_DECODER_PROFILE_HEVC_VLD_MAIN12_444, false, AV_PIX_FMT_YUV444P12 },
	// HEVC Intel
	{ AV_CODEC_ID_HEVC, DXVA2_HEVC_VLD_Main12_Intel,     false, AV_PIX_FMT_YUV420P12 },
	{ AV_CODEC_ID_HEVC, DXVA2_HEVC_VLD_Main422_10_Intel, false, AV_PIX_FMT_YUV422P   },
	{ AV_CODEC_ID_HEVC, DXVA2_HEVC_VLD_Main422_10_Intel, false, AV_PIX_FMT_YUV422P10 },
	{ AV_CODEC_ID_HEVC, DXVA2_HEVC_VLD_Main422_12_Intel, false, AV_PIX_FMT_YUV422P12 },
	{ AV_CODEC_ID_HEVC, DXVA2_HEVC_VLD_Main444_Intel,    false, AV_PIX_FMT_YUV444P   },
	{ AV_CODEC_ID_HEVC, DXVA2_HEVC_VLD_Main444_10_Intel, false, AV_PIX_FMT_YUV444P10 },
	{ AV_CODEC_ID_HEVC, DXVA2_HEVC_VLD_Main444_12_Intel, false, AV_PIX_FMT_YUV444P12 },
	// HEVC
	{ AV_CODEC_ID_HEVC, DXVA2_ModeHEVC_VLD_Main10, true},
	{ AV_CODEC_ID_HEVC, DXVA2_ModeHEVC_VLD_Main, false},
	// MPEG2
	{ AV_CODEC_ID_MPEG2VIDEO, DXVA2_ModeMPEG2_VLD, false },
	// VC1
	{ AV_CODEC_ID_VC1, DXVA2_ModeVC1_D2010, false },
	{ AV_CODEC_ID_VC1, DXVA2_ModeVC1_D, false },
	// WMV3
	{ AV_CODEC_ID_WMV3, DXVA2_ModeVC1_D2010, false },
	{ AV_CODEC_ID_WMV3, DXVA2_ModeVC1_D, false },
	// VP9
	{ AV_CODEC_ID_VP9, DXVA2_ModeVP9_VLD_10bit_Profile2, true },
	{ AV_CODEC_ID_VP9, DXVA2_ModeVP9_VLD_Profile0, false },
	// AV1
	{ AV_CODEC_ID_AV1, DXVA2_ModeAV1_VLD_Profile0, true },
	{ AV_CODEC_ID_AV1, DXVA2_ModeAV1_VLD_Profile0, false }
};

FFMPEG_CODECS ffCodecs[] = {
	// Flash video
	{ &MEDIASUBTYPE_FLV1, AV_CODEC_ID_FLV1, VDEC_FLV, HWCodec_None },
	{ &MEDIASUBTYPE_flv1, AV_CODEC_ID_FLV1, VDEC_FLV, HWCodec_None },
	{ &MEDIASUBTYPE_FLV4, AV_CODEC_ID_VP6F, VDEC_FLV, HWCodec_None },
	{ &MEDIASUBTYPE_flv4, AV_CODEC_ID_VP6F, VDEC_FLV, HWCodec_None },
	{ &MEDIASUBTYPE_VP6F, AV_CODEC_ID_VP6F, VDEC_FLV, HWCodec_None },
	{ &MEDIASUBTYPE_vp6f, AV_CODEC_ID_VP6F, VDEC_FLV, HWCodec_None },

	// VP3
	{ &MEDIASUBTYPE_VP30, AV_CODEC_ID_VP3,  VDEC_VP356, HWCodec_None },
	{ &MEDIASUBTYPE_VP31, AV_CODEC_ID_VP3,  VDEC_VP356, HWCodec_None },

	// VP5
	{ &MEDIASUBTYPE_VP50, AV_CODEC_ID_VP5,  VDEC_VP356, HWCodec_None },
	{ &MEDIASUBTYPE_vp50, AV_CODEC_ID_VP5,  VDEC_VP356, HWCodec_None },

	// VP6
	{ &MEDIASUBTYPE_VP60, AV_CODEC_ID_VP6,  VDEC_VP356, HWCodec_None },
	{ &MEDIASUBTYPE_vp60, AV_CODEC_ID_VP6,  VDEC_VP356, HWCodec_None },
	{ &MEDIASUBTYPE_VP61, AV_CODEC_ID_VP6,  VDEC_VP356, HWCodec_None },
	{ &MEDIASUBTYPE_vp61, AV_CODEC_ID_VP6,  VDEC_VP356, HWCodec_None },
	{ &MEDIASUBTYPE_VP62, AV_CODEC_ID_VP6,  VDEC_VP356, HWCodec_None },
	{ &MEDIASUBTYPE_vp62, AV_CODEC_ID_VP6,  VDEC_VP356, HWCodec_None },
	{ &MEDIASUBTYPE_VP6A, AV_CODEC_ID_VP6A, VDEC_VP356, HWCodec_None },
	{ &MEDIASUBTYPE_vp6a, AV_CODEC_ID_VP6A, VDEC_VP356, HWCodec_None },

	// VP7
	{ &MEDIASUBTYPE_VP70, AV_CODEC_ID_VP7, VDEC_VP789, HWCodec_None },

	// VP8
	{ &MEDIASUBTYPE_VP80, AV_CODEC_ID_VP8, VDEC_VP789, HWCodec_None },

	// VP9
	{ &MEDIASUBTYPE_VP90, AV_CODEC_ID_VP9, VDEC_VP789, HWCodec_VP9 },

	// Xvid
	{ &MEDIASUBTYPE_XVID, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_xvid, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_XVIX, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_xvix, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },

	// DivX
	{ &MEDIASUBTYPE_DX50, AV_CODEC_ID_MPEG4, VDEC_DIVX, HWCodec_None },
	{ &MEDIASUBTYPE_dx50, AV_CODEC_ID_MPEG4, VDEC_DIVX, HWCodec_None },
	{ &MEDIASUBTYPE_DIVX, AV_CODEC_ID_MPEG4, VDEC_DIVX, HWCodec_None },
	{ &MEDIASUBTYPE_divx, AV_CODEC_ID_MPEG4, VDEC_DIVX, HWCodec_None },
	{ &MEDIASUBTYPE_Divx, AV_CODEC_ID_MPEG4, VDEC_DIVX, HWCodec_None },

	// WMV1/2/3
	{ &MEDIASUBTYPE_WMV1, AV_CODEC_ID_WMV1, VDEC_WMV, HWCodec_None },
	{ &MEDIASUBTYPE_wmv1, AV_CODEC_ID_WMV1, VDEC_WMV, HWCodec_None },
	{ &MEDIASUBTYPE_WMV2, AV_CODEC_ID_WMV2, VDEC_WMV, HWCodec_None },
	{ &MEDIASUBTYPE_wmv2, AV_CODEC_ID_WMV2, VDEC_WMV, HWCodec_None },
	{ &MEDIASUBTYPE_WMV3, AV_CODEC_ID_WMV3, VDEC_WMV, HWCodec_WMV3 },
	{ &MEDIASUBTYPE_wmv3, AV_CODEC_ID_WMV3, VDEC_WMV, HWCodec_WMV3 },
	// WMVP
	{ &MEDIASUBTYPE_WMVP, AV_CODEC_ID_WMV3IMAGE, VDEC_WMV, HWCodec_None },

	// MPEG-2
	{ &MEDIASUBTYPE_MPEG2_VIDEO, AV_CODEC_ID_MPEG2VIDEO, VDEC_MPEG2, HWCodec_MPEG2 },
	{ &MEDIASUBTYPE_MPG2,        AV_CODEC_ID_MPEG2VIDEO, VDEC_MPEG2, HWCodec_MPEG2 },

	// MPEG-1
	{ &MEDIASUBTYPE_MPEG1Packet,  AV_CODEC_ID_MPEG1VIDEO, VDEC_MPEG1, HWCodec_None },
	{ &MEDIASUBTYPE_MPEG1Payload, AV_CODEC_ID_MPEG1VIDEO, VDEC_MPEG1, HWCodec_None },

	// MSMPEG-4
	{ &MEDIASUBTYPE_DIV3, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_div3, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_DVX3, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_dvx3, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_MP43, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_mp43, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_COL1, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_col1, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_DIV4, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_div4, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_DIV5, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_div5, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_DIV6, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_div6, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_AP41, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_ap41, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_MPG3, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_mpg3, AV_CODEC_ID_MSMPEG4V3, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_DIV2, AV_CODEC_ID_MSMPEG4V2, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_div2, AV_CODEC_ID_MSMPEG4V2, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_MP42, AV_CODEC_ID_MSMPEG4V2, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_mp42, AV_CODEC_ID_MSMPEG4V2, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_MPG4, AV_CODEC_ID_MSMPEG4V1, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_mpg4, AV_CODEC_ID_MSMPEG4V1, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_DIV1, AV_CODEC_ID_MSMPEG4V1, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_div1, AV_CODEC_ID_MSMPEG4V1, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_MP41, AV_CODEC_ID_MSMPEG4V1, VDEC_MSMPEG4, HWCodec_None },
	{ &MEDIASUBTYPE_mp41, AV_CODEC_ID_MSMPEG4V1, VDEC_MSMPEG4, HWCodec_None },

	// AMV Video
	{ &MEDIASUBTYPE_AMVV, AV_CODEC_ID_AMV, VDEC_AMV, HWCodec_None },

	// MJPEG
	{ &MEDIASUBTYPE_MJPG,   AV_CODEC_ID_MJPEG,    VDEC_MJPEG, HWCodec_None },
	{ &MEDIASUBTYPE_QTJpeg, AV_CODEC_ID_MJPEG,    VDEC_MJPEG, HWCodec_None },
	{ &MEDIASUBTYPE_MJPA,   AV_CODEC_ID_MJPEG,    VDEC_MJPEG, HWCodec_None },
	{ &MEDIASUBTYPE_MJPB,   AV_CODEC_ID_MJPEGB,   VDEC_MJPEG, HWCodec_None },
	{ &MEDIASUBTYPE_MJP2,   AV_CODEC_ID_JPEG2000, VDEC_MJPEG, HWCodec_None },
	{ &MEDIASUBTYPE_MJ2C,   AV_CODEC_ID_JPEG2000, VDEC_MJPEG, HWCodec_None },

	// Cinepak
	{ &MEDIASUBTYPE_CVID, AV_CODEC_ID_CINEPAK, VDEC_CINEPAK, HWCodec_None },

	// DV VIDEO
	{ &MEDIASUBTYPE_dvsl, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_dvsd, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_dvhd, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_dv25, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_dv50, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_dvh1, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_CDVH, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_CDVC, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	// Quicktime DV sybtypes (used in LAV Splitter)
	{ &MEDIASUBTYPE_DVCP, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_DVPP, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_DV5P, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_DVC,  AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_DVH2, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_DVH3, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_DVH4, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_DVH5, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_DVH6, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_DVHQ, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_DVHP, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_AVdv, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },
	{ &MEDIASUBTYPE_AVd1, AV_CODEC_ID_DVVIDEO, VDEC_DV, HWCodec_None },

	// Quicktime
	{ &MEDIASUBTYPE_8BPS,   AV_CODEC_ID_8BPS,  VDEC_QT, HWCodec_None },
	{ &MEDIASUBTYPE_QTRle,  AV_CODEC_ID_QTRLE, VDEC_QT, HWCodec_None },
	{ &MEDIASUBTYPE_QTRpza, AV_CODEC_ID_RPZA,  VDEC_QT, HWCodec_None },

	// Screen recorder
	{ &MEDIASUBTYPE_CSCD,     AV_CODEC_ID_CSCD,     VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_TSCC,     AV_CODEC_ID_TSCC,     VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_TSCC2,    AV_CODEC_ID_TSCC2,    VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_VMnc,     AV_CODEC_ID_VMNC,     VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_FLASHSV1, AV_CODEC_ID_FLASHSV,  VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_FLASHSV2, AV_CODEC_ID_FLASHSV2, VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_FPS1,     AV_CODEC_ID_FRAPS,    VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_MSS1,     AV_CODEC_ID_MSS1,     VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_MSS2,     AV_CODEC_ID_MSS2,     VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_MSA1,     AV_CODEC_ID_MSA1,     VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_MTS2,     AV_CODEC_ID_MTS2,     VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_G2M2,     AV_CODEC_ID_G2M,      VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_G2M3,     AV_CODEC_ID_G2M,      VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_G2M4,     AV_CODEC_ID_G2M,      VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_CRAM,     AV_CODEC_ID_MSVIDEO1, VDEC_SCREEN, HWCodec_None }, // CRAM - Microsoft Video 1
	{ &MEDIASUBTYPE_FICV,     AV_CODEC_ID_FIC,      VDEC_SCREEN, HWCodec_None },
	{ &MEDIASUBTYPE_DXTORY,   AV_CODEC_ID_DXTORY,   VDEC_SCREEN, HWCodec_None },

	// UtVideo
	{ &MEDIASUBTYPE_Ut_ULRA, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_ULRG, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_ULY0, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_ULY2, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_ULY4, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_ULH0, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_ULH2, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_ULH4, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	// UtVideo T2
	{ &MEDIASUBTYPE_Ut_UMRA, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_UMRG, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_UMY2, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_UMY4, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_UMH2, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_UMH4, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	// UtVideo Pro
	{ &MEDIASUBTYPE_Ut_UQRA, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_UQRG, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_UQY0, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },
	{ &MEDIASUBTYPE_Ut_UQY2, AV_CODEC_ID_UTVIDEO, VDEC_UT, HWCodec_None },

	// DIRAC
	{ &MEDIASUBTYPE_DRAC, AV_CODEC_ID_DIRAC, VDEC_DIRAC, HWCodec_None },

	// Lossless Video
	{ &MEDIASUBTYPE_HuffYUV,  AV_CODEC_ID_HUFFYUV,  VDEC_LOSSLESS, HWCodec_None },
	{ &MEDIASUBTYPE_HYMT,     AV_CODEC_ID_HYMT,     VDEC_LOSSLESS, HWCodec_None },
	{ &MEDIASUBTYPE_FFVHuff,  AV_CODEC_ID_FFVHUFF,  VDEC_LOSSLESS, HWCodec_None },
	{ &MEDIASUBTYPE_FFV1,     AV_CODEC_ID_FFV1,     VDEC_LOSSLESS, HWCodec_None },
	{ &MEDIASUBTYPE_Lagarith, AV_CODEC_ID_LAGARITH, VDEC_LOSSLESS, HWCodec_None },
	{ &MEDIASUBTYPE_MAGICYUV, AV_CODEC_ID_MAGICYUV, VDEC_LOSSLESS, HWCodec_None },

	// Indeo 3/4/5
	{ &MEDIASUBTYPE_IV31, AV_CODEC_ID_INDEO3, VDEC_INDEO, HWCodec_None },
	{ &MEDIASUBTYPE_IV32, AV_CODEC_ID_INDEO3, VDEC_INDEO, HWCodec_None },
	{ &MEDIASUBTYPE_IV41, AV_CODEC_ID_INDEO4, VDEC_INDEO, HWCodec_None },
	{ &MEDIASUBTYPE_IV50, AV_CODEC_ID_INDEO5, VDEC_INDEO, HWCodec_None },

	// H264/AVC
	{ &MEDIASUBTYPE_H264,     AV_CODEC_ID_H264, VDEC_H264, HWCodec_H264 },
	{ &MEDIASUBTYPE_h264,     AV_CODEC_ID_H264, VDEC_H264, HWCodec_H264 },
	{ &MEDIASUBTYPE_X264,     AV_CODEC_ID_H264, VDEC_H264, HWCodec_H264 },
	{ &MEDIASUBTYPE_x264,     AV_CODEC_ID_H264, VDEC_H264, HWCodec_H264 },
	{ &MEDIASUBTYPE_VSSH,     AV_CODEC_ID_H264, VDEC_H264, HWCodec_H264 },
	{ &MEDIASUBTYPE_vssh,     AV_CODEC_ID_H264, VDEC_H264, HWCodec_H264 },
	{ &MEDIASUBTYPE_DAVC,     AV_CODEC_ID_H264, VDEC_H264, HWCodec_H264 },
	{ &MEDIASUBTYPE_davc,     AV_CODEC_ID_H264, VDEC_H264, HWCodec_H264 },
	{ &MEDIASUBTYPE_PAVC,     AV_CODEC_ID_H264, VDEC_H264, HWCodec_H264 },
	{ &MEDIASUBTYPE_pavc,     AV_CODEC_ID_H264, VDEC_H264, HWCodec_H264 },
	{ &MEDIASUBTYPE_AVC1,     AV_CODEC_ID_H264, VDEC_H264, HWCodec_H264 },
	{ &MEDIASUBTYPE_avc1,     AV_CODEC_ID_H264, VDEC_H264, HWCodec_H264 },
	{ &MEDIASUBTYPE_H264_bis, AV_CODEC_ID_H264, VDEC_H264, HWCodec_H264 },

	// H264 MVC
	{ &MEDIASUBTYPE_AMVC, AV_CODEC_ID_H264, VDEC_H264_MVC, HWCodec_None },
	{ &MEDIASUBTYPE_MVC1, AV_CODEC_ID_H264, VDEC_H264_MVC, HWCodec_None },

	// SVQ3
	{ &MEDIASUBTYPE_SVQ3, AV_CODEC_ID_SVQ3, VDEC_SVQ, HWCodec_None },
	// SVQ1
	{ &MEDIASUBTYPE_SVQ1, AV_CODEC_ID_SVQ1, VDEC_SVQ, HWCodec_None },

	// H263
	{ &MEDIASUBTYPE_H263, AV_CODEC_ID_H263, VDEC_H263, HWCodec_None },
	{ &MEDIASUBTYPE_h263, AV_CODEC_ID_H263, VDEC_H263, HWCodec_None },
	{ &MEDIASUBTYPE_S263, AV_CODEC_ID_H263, VDEC_H263, HWCodec_None },
	{ &MEDIASUBTYPE_s263, AV_CODEC_ID_H263, VDEC_H263, HWCodec_None },
	{ &MEDIASUBTYPE_I263, AV_CODEC_ID_H263I, VDEC_H263, HWCodec_None },

	// Real Video
	{ &MEDIASUBTYPE_RV10, AV_CODEC_ID_RV10, VDEC_REAL, HWCodec_None },
	{ &MEDIASUBTYPE_RV20, AV_CODEC_ID_RV20, VDEC_REAL, HWCodec_None },
	{ &MEDIASUBTYPE_RV30, AV_CODEC_ID_RV30, VDEC_REAL, HWCodec_None },
	{ &MEDIASUBTYPE_RV40, AV_CODEC_ID_RV40, VDEC_REAL, HWCodec_None },

	// Theora
	{ &MEDIASUBTYPE_THEORA, AV_CODEC_ID_THEORA, VDEC_THEORA, HWCodec_None },
	{ &MEDIASUBTYPE_theora, AV_CODEC_ID_THEORA, VDEC_THEORA, HWCodec_None },

	// WVC1
	{ &MEDIASUBTYPE_WVC1, AV_CODEC_ID_VC1, VDEC_VC1, HWCodec_VC1 },
	{ &MEDIASUBTYPE_wvc1, AV_CODEC_ID_VC1, VDEC_VC1, HWCodec_VC1 },

	// WMVA
	{ &MEDIASUBTYPE_WMVA, AV_CODEC_ID_VC1, VDEC_VC1, HWCodec_VC1 },

	// WVP2
	{ &MEDIASUBTYPE_WVP2, AV_CODEC_ID_VC1IMAGE, VDEC_VC1, HWCodec_None },

	// Apple ProRes
	{ &MEDIASUBTYPE_apch, AV_CODEC_ID_PRORES, VDEC_PRORES, HWCodec_None },
	{ &MEDIASUBTYPE_apcn, AV_CODEC_ID_PRORES, VDEC_PRORES, HWCodec_None },
	{ &MEDIASUBTYPE_apcs, AV_CODEC_ID_PRORES, VDEC_PRORES, HWCodec_None },
	{ &MEDIASUBTYPE_apco, AV_CODEC_ID_PRORES, VDEC_PRORES, HWCodec_None },
	{ &MEDIASUBTYPE_ap4h, AV_CODEC_ID_PRORES, VDEC_PRORES, HWCodec_None },
	{ &MEDIASUBTYPE_ap4x, AV_CODEC_ID_PRORES, VDEC_PRORES, HWCodec_None },
	{ &MEDIASUBTYPE_icpf, AV_CODEC_ID_PRORES, VDEC_PRORES, HWCodec_None },
	{ &MEDIASUBTYPE_icod, AV_CODEC_ID_AIC,    VDEC_PRORES, HWCodec_None },

	// Bink Video
	{ &MEDIASUBTYPE_BINKVI, AV_CODEC_ID_BINKVIDEO, VDEC_BINK, HWCodec_None },
	{ &MEDIASUBTYPE_BINKVB, AV_CODEC_ID_BINKVIDEO, VDEC_BINK, HWCodec_None },

	// PNG
	{ &MEDIASUBTYPE_PNG, AV_CODEC_ID_PNG, VDEC_PNG, HWCodec_None },

	// Canopus
	{ &MEDIASUBTYPE_CLLC, AV_CODEC_ID_CLLC,   VDEC_CANOPUS, HWCodec_None },
	{ &MEDIASUBTYPE_CUVC, AV_CODEC_ID_HQ_HQA, VDEC_CANOPUS, HWCodec_None },
	{ &MEDIASUBTYPE_CHQX, AV_CODEC_ID_HQX,    VDEC_CANOPUS, HWCodec_None },

	// CineForm
	{ &MEDIASUBTYPE_CFHD, AV_CODEC_ID_CFHD, VDEC_CINEFORM, HWCodec_None },

	// HEVC
	{ &MEDIASUBTYPE_HEVC, AV_CODEC_ID_HEVC, VDEC_HEVC, HWCodec_HEVC },
	{ &MEDIASUBTYPE_HVC1, AV_CODEC_ID_HEVC, VDEC_HEVC, HWCodec_HEVC },
	{ &MEDIASUBTYPE_hvc1, AV_CODEC_ID_HEVC, VDEC_HEVC, HWCodec_HEVC },
	{ &MEDIASUBTYPE_HM10, AV_CODEC_ID_HEVC, VDEC_HEVC, HWCodec_HEVC },

	// Avid DNxHD
	{ &MEDIASUBTYPE_AVdn, AV_CODEC_ID_DNXHD, VDEC_DNXHD, HWCodec_None },
	// Avid DNxHR
	{ &MEDIASUBTYPE_AVdh, AV_CODEC_ID_DNXHD, VDEC_DNXHD, HWCodec_None },

	// Other MPEG-4
	{ &MEDIASUBTYPE_MP4V, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_mp4v, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_M4S2, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_m4s2, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_MP4S, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_mp4s, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_3IV1, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_3iv1, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_3IV2, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_3iv2, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_3IVX, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_3ivx, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_BLZ0, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_blz0, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_DM4V, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_dm4v, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_FFDS, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_ffds, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_FVFW, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_fvfw, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_DXGM, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_dxgm, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_FMP4, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_fmp4, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_HDX4, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_hdx4, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_LMP4, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_lmp4, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_NDIG, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_ndig, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_RMP4, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_rmp4, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_SMP4, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_smp4, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_SEDG, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_sedg, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_UMP4, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_ump4, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_WV1F, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },
	{ &MEDIASUBTYPE_wv1f, AV_CODEC_ID_MPEG4, VDEC_XVID, HWCodec_None },

	// Vidvox Hap
	{ &MEDIASUBTYPE_Hap1, AV_CODEC_ID_HAP, VDEC_HAP, HWCodec_None },
	{ &MEDIASUBTYPE_Hap5, AV_CODEC_ID_HAP, VDEC_HAP, HWCodec_None },
	{ &MEDIASUBTYPE_HapA, AV_CODEC_ID_HAP, VDEC_HAP, HWCodec_None },
	{ &MEDIASUBTYPE_HapM, AV_CODEC_ID_HAP, VDEC_HAP, HWCodec_None },
	{ &MEDIASUBTYPE_HapY, AV_CODEC_ID_HAP, VDEC_HAP, HWCodec_None },

	// AV1
	{ &MEDIASUBTYPE_AV01, AV_CODEC_ID_AV1, VDEC_AV1, HWCodec_AV1 },
	{ &MEDIASUBTYPE_av01, AV_CODEC_ID_AV1, VDEC_AV1, HWCodec_AV1 },

	// SpeedHQ
	{ &MEDIASUBTYPE_SHQ0, AV_CODEC_ID_SPEEDHQ, VDEC_SHQ, HWCodec_None },
	{ &MEDIASUBTYPE_SHQ1, AV_CODEC_ID_SPEEDHQ, VDEC_SHQ, HWCodec_None },
	{ &MEDIASUBTYPE_SHQ2, AV_CODEC_ID_SPEEDHQ, VDEC_SHQ, HWCodec_None },
	{ &MEDIASUBTYPE_SHQ3, AV_CODEC_ID_SPEEDHQ, VDEC_SHQ, HWCodec_None },
	{ &MEDIASUBTYPE_SHQ4, AV_CODEC_ID_SPEEDHQ, VDEC_SHQ, HWCodec_None },
	{ &MEDIASUBTYPE_SHQ5, AV_CODEC_ID_SPEEDHQ, VDEC_SHQ, HWCodec_None },
	{ &MEDIASUBTYPE_SHQ7, AV_CODEC_ID_SPEEDHQ, VDEC_SHQ, HWCodec_None },
	{ &MEDIASUBTYPE_SHQ9, AV_CODEC_ID_SPEEDHQ, VDEC_SHQ, HWCodec_None },

	// AVS3
	{ &MEDIASUBTYPE_AVS3, AV_CODEC_ID_AVS3, VDEC_AVS3, HWCodec_None },

	// VVC
	{ &MEDIASUBTYPE_VVC1, AV_CODEC_ID_VVC, VDEC_VVC, HWCodec_None },

	// uncompressed video
	{ &MEDIASUBTYPE_v210, AV_CODEC_ID_V210, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_V410, AV_CODEC_ID_V410, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_r210, AV_CODEC_ID_R210, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_R10g, AV_CODEC_ID_R10K, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_R10k, AV_CODEC_ID_R10K, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_AVrp, AV_CODEC_ID_AVRP, VDEC_UNCOMPRESSED, HWCodec_None },

	{ &MEDIASUBTYPE_Y8,   AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_Y800, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_Y16,  AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_I420, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_Y41B, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_Y42B, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_444P, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_cyuv, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },

	{ &MEDIASUBTYPE_YVU9, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_IYUV, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_UYVY, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_YUY2, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },

	{ &MEDIASUBTYPE_NV12, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_YV12, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_YV16, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_YV24, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },

	{ &MEDIASUBTYPE_BGR48,  AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_BGRA64, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_b48r,   AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },
	{ &MEDIASUBTYPE_b64a,   AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None },

	{ &MEDIASUBTYPE_LAV_RAWVIDEO, AV_CODEC_ID_RAWVIDEO, VDEC_UNCOMPRESSED, HWCodec_None }
};

/* Important: the order should be exactly the same as in ffCodecs[] */
const AMOVIESETUP_MEDIATYPE sudPinTypesIn[] = {
	// Flash video
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_FLV1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_flv1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_FLV4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_flv4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VP6F },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_vp6f },

	// VP3
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VP30 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VP31 },

	// VP5
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VP50 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_vp50 },

	// VP6
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VP60 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_vp60 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VP61 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_vp61 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VP62 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_vp62 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VP6A },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_vp6a },

	// VP7
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VP70 },

	// VP8
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VP80 },

	// VP9
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VP90 },

	// Xvid
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_XVID },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_xvid },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_XVIX },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_xvix },

	// DivX
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DX50 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_dx50 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DIVX },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_divx },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Divx },

	// WMV1/2/3
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_WMV1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_wmv1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_WMV2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_wmv2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_WMV3 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_wmv3 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_WMVP },

	// MPEG-2
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MPEG2_VIDEO },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MPG2        },

	// MPEG-1
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MPEG1Packet  },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MPEG1Payload },

	// MSMPEG-4
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DIV3 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_div3 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DVX3 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_dvx3 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MP43 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_mp43 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_COL1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_col1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DIV4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_div4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DIV5 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_div5 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DIV6 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_div6 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_AP41 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_ap41 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MPG3 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_mpg3 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DIV2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_div2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MP42 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_mp42 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MPG4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_mpg4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DIV1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_div1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MP41 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_mp41 },

	// AMV Video
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_AMVV },

	// MJPEG
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MJPG   },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_QTJpeg },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MJPA   },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MJPB   },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MJP2   },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MJ2C   },

	// CINEPAK
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_CVID },

	// DV VIDEO
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_dvsl },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_dvsd },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_dvhd },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_dv25 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_dv50 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_dvh1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_CDVH },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_CDVC },
	// Quicktime DV sybtypes (used in LAV Splitter)
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DVCP },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DVPP },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DV5P },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DVC  },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DVH2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DVH3 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DVH4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DVH5 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DVH6 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DVHQ },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DVHP },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_AVdv },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_AVd1 },

	// QuickTime video
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_8BPS   },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_QTRle  },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_QTRpza },

	// Screen recorder
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_CSCD },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_TSCC },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_TSCC2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VMnc },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_FLASHSV1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_FLASHSV2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_FPS1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MSS1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MSS2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MSA1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MTS2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_G2M2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_G2M3 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_G2M4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_CRAM }, // CRAM - Microsoft Video 1
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_FICV },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DXTORY },

	// UtVideo
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_ULRA },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_ULRG },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_ULY0 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_ULY2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_ULY4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_ULH0 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_ULH2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_ULH4 },
	// UtVideo T2
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_UMRA },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_UMRG },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_UMY2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_UMY4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_UMH2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_UMH4 },
	// UtVideo Pro
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_UQRA },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_UQRG },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_UQY0 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Ut_UQY2 },

	// DIRAC
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DRAC },

	// Lossless Video
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_HuffYUV  },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_HYMT     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_FFVHuff  },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_FFV1     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Lagarith },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MAGICYUV },

	// Indeo 3/4/5
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_IV31 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_IV32 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_IV41 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_IV50 },

	// H264/AVC
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_H264     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_h264     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_X264     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_x264     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VSSH     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_vssh     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DAVC     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_davc     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_PAVC     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_pavc     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_AVC1     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_avc1     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_H264_bis },

	// H264 MVC
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_AMVC     },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MVC1     },

	// SVQ3
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_SVQ3 },

	// SVQ1
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_SVQ1 },

	// H263
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_H263 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_h263 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_S263 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_s263 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_I263 },

	// Real video
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_RV10 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_RV20 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_RV30 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_RV40 },

	// Theora
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_THEORA },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_theora },

	// VC1
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_WVC1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_wvc1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_WMVA },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_WVP2 },

	// Apple ProRes
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_apch },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_apcn },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_apcs },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_apco },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_ap4h },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_ap4x },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_icpf },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_icod },

	// Bink Video
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_BINKVI },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_BINKVB },

	// PNG
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_PNG },

	// Canopus
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_CLLC },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_CUVC },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_CHQX },

	// CineForm
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_CFHD },

	// HEVC
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_HEVC },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_HVC1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_hvc1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_HM10 },

	// Avid DNxHD
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_AVdn },
	// Avid DNxHR
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_AVdh },

	// Other MPEG-4
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MP4V },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_mp4v },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_M4S2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_m4s2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_MP4S },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_mp4s },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_3IV1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_3iv1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_3IV2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_3iv2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_3IVX },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_3ivx },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_BLZ0 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_blz0 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DM4V },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_dm4v },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_FFDS },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_ffds },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_FVFW },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_fvfw },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_DXGM },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_dxgm },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_FMP4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_fmp4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_HDX4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_hdx4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_LMP4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_lmp4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_NDIG },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_ndig },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_RMP4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_rmp4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_SMP4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_smp4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_SEDG },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_sedg },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_UMP4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_ump4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_WV1F },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_wv1f },

	// Vidvox Hap
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Hap1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Hap5 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_HapA },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_HapM },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_HapY },

	// AV1
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_AV01 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_av01 },

	// SpeedHQ
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_SHQ0 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_SHQ1 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_SHQ2 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_SHQ3 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_SHQ4 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_SHQ5 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_SHQ7 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_SHQ9 },

	// AVS3
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_AVS3 },

	// VVC
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_VVC1 }
};

const AMOVIESETUP_MEDIATYPE sudPinTypesInUncompressed[] = {
	// uncompressed video
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_v210 }, // YUV 4:2:2 10-bit
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_V410 }, // YUV 4:4:4 10-bit
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_r210 }, // RGB30
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_R10g }, // RGB30
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_R10k }, // RGB30
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_AVrp }, // RGB30

	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Y8   }, // Y 8-bit (monochrome)
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Y800 }, // Y 8-bit (monochrome)
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Y16  }, // Y 16-bit (monochrome)
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_I420 }, // YUV 4:2:0 Planar
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Y41B }, // YUV 4:1:1 Planar
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_Y42B }, // YUV 4:2:2 Planar
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_444P }, // YUV 4:4:4 Planar
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_cyuv }, // UYVY flipped vertically

	{ &MEDIATYPE_Video, &MEDIASUBTYPE_YVU9 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_IYUV },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_UYVY },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_YUY2 },

	{ &MEDIATYPE_Video, &MEDIASUBTYPE_NV12 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_YV12 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_YV16 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_YV24 },

	{ &MEDIATYPE_Video, &MEDIASUBTYPE_BGR48 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_BGRA64 },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_b48r },
	{ &MEDIATYPE_Video, &MEDIASUBTYPE_b64a },

	{ &MEDIATYPE_Video, &MEDIASUBTYPE_LAV_RAWVIDEO },
};

#pragma endregion any_constants

const AMOVIESETUP_MEDIATYPE sudPinTypesOut[] = {
	{&MEDIATYPE_Video, &MEDIASUBTYPE_NV12},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_YV12},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_YUY2},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_YV16},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_AYUV},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_YV24},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_P010},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_P210},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_Y410},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_P016},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_P216},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_Y416},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_RGB32},
};

#ifdef REGISTER_FILTER

const AMOVIESETUP_PIN sudpPins[] = {
	{(LPWSTR)L"Input", FALSE, FALSE, FALSE, FALSE, &CLSID_NULL, nullptr, std::size(sudPinTypesIn),  sudPinTypesIn},
	{(LPWSTR)L"Output", FALSE, TRUE, FALSE, FALSE, &CLSID_NULL, nullptr, std::size(sudPinTypesOut), sudPinTypesOut}
};

const AMOVIESETUP_PIN sudpPinsUncompressed[] = {
	{(LPWSTR)L"Input", FALSE, FALSE, FALSE, FALSE, &CLSID_NULL, nullptr, std::size(sudPinTypesInUncompressed), sudPinTypesInUncompressed},
	{(LPWSTR)L"Output", FALSE, TRUE, FALSE, FALSE, &CLSID_NULL, nullptr, std::size(sudPinTypesOut), sudPinTypesOut}
};

CLSID Converter_clsID = GUIDFromCString(L"{0B7FA55E-FA38-4671-A2F2-B8F300C955C4}");

const AMOVIESETUP_FILTER sudFilters[] = {
	{&__uuidof(CMPCVideoDecFilter), MPCVideoDecName, MERIT_NORMAL + 1, std::size(sudpPins), sudpPins, CLSID_LegacyAmFilterCategory},
	{&Converter_clsID, MPCVideoConvName, MERIT_NORMAL + 1, std::size(sudpPinsUncompressed), sudpPinsUncompressed, CLSID_LegacyAmFilterCategory}
	// merit of video converter must be lower than merit of video renderers
};

CFactoryTemplate g_Templates[] = {
	{sudFilters[0].strName, sudFilters[0].clsID, CreateInstance<CMPCVideoDecFilter>, nullptr, &sudFilters[0]},
	{sudFilters[1].strName, sudFilters[1].clsID, CreateInstance<CMPCVideoDecFilter>, nullptr, &sudFilters[1]},
	{L"CMPCVideoDecPropertyPage", &__uuidof(CMPCVideoDecSettingsWnd), CreateInstance<CInternalPropertyPageTempl<CMPCVideoDecSettingsWnd> >},
	{L"CMPCVideoDecPropertyPage2", &__uuidof(CMPCVideoDecCodecWnd), CreateInstance<CInternalPropertyPageTempl<CMPCVideoDecCodecWnd> >},
};

int g_cTemplates = std::size(g_Templates);

STDAPI DllRegisterServer()
{
	return AMovieDllRegisterServer2(TRUE);
}

STDAPI DllUnregisterServer()
{
	return AMovieDllRegisterServer2(FALSE);
}

#include "filters/filters/Filters.h"
#include "filters/ffmpeg_link_fix.h"

CFilterApp theApp;

#else

#include "DSUtil/Profile.h"

#endif

static CStringA AVError2Str(const int errnum)
{
	char errBuf[AV_ERROR_MAX_STRING_SIZE] = {};
	return CStringA(av_make_error_string(errBuf, sizeof(errBuf), errnum));
}

BOOL CALLBACK EnumFindProcessWnd (HWND hwnd, LPARAM lParam)
{
	DWORD procId = 0;
	WCHAR wndClassName[40];
	GetWindowThreadProcessId(hwnd, &procId);
	GetClassNameW(hwnd, wndClassName, std::size(wndClassName));

	if (procId == GetCurrentProcessId() && wcscmp(wndClassName, MPC_WND_CLASS_NAMEW) == 0) {
		HWND* pWnd = (HWND*) lParam;
		*pWnd = hwnd;
		return FALSE;
	}
	return TRUE;
}

// CMPCVideoDecFilter

CMPCVideoDecFilter::CMPCVideoDecFilter(LPUNKNOWN lpunk, HRESULT* phr)
	: CBaseVideoFilter(L"MPC - Video decoder", lpunk, phr, __uuidof(this))
	, m_nDiscardMode(AVDISCARD_DEFAULT)
	, m_nHwDecoder(SysVersion::IsWin8orLater() ? HWDec_D3D11 : HWDec_DXVA2)
	, m_CodecId(AV_CODEC_ID_NONE)
	, m_dxva_pix_fmt(AV_PIX_FMT_NONE)
	, m_HWPixFmt(AV_PIX_FMT_NONE)
{
	if (phr) {
		*phr = S_OK;
	}

	m_pInput = DNew CBaseVideoInputPin(L"CBaseVideoInputPin", this, phr, L"Video");
	if (!m_pInput) {
		*phr = E_OUTOFMEMORY;
	}
	if (FAILED(*phr)) {
		return;
	}

	m_pOutput = DNew CVideoDecOutputPin(L"CVideoDecOutputPin", this, phr, L"Output");
	if (!m_pOutput) {
		*phr = E_OUTOFMEMORY;
	}
	if (FAILED(*phr)) {
		return;
	}

	for (int i = 0; i < HWCodec_count; i++) {
		m_bHwCodecs[i] = true;
	}
	for (int i = 0; i < PixFmt_count; i++) {
		if (i == PixFmt_AYUV || i == PixFmt_YUV444P16 || i == PixFmt_RGB48) {
			m_fPixFmts[i] = false;
		} else {
			m_fPixFmts[i] = true;
		}
	}

	// Enable by default - used by MPC Video Converter
	m_VideoFilters[VDEC_UNCOMPRESSED] = true;

#ifdef REGISTER_FILTER
	CRegKey key;
	WCHAR buff[256];
	ULONG len;

	if (ERROR_SUCCESS == key.Open(HKEY_CURRENT_USER, OPT_REGKEY_VideoDec, KEY_READ)) {
		DWORD dw;
		if (ERROR_SUCCESS == key.QueryDWORDValue(OPT_ThreadNumber, dw)) {
			m_nThreadNumber = dw;
		}
		if (ERROR_SUCCESS == key.QueryDWORDValue(OPT_DiscardMode, dw)) {
			m_nDiscardMode = dw;
		}
		if (ERROR_SUCCESS == key.QueryDWORDValue(OPT_ScanType, dw)) {
			m_nScanType = (MPC_SCAN_TYPE)dw;
		}
		if (ERROR_SUCCESS == key.QueryDWORDValue(OPT_ARMode, dw)) {
			m_nARMode = dw;
		}

		for (int i = 0; i < HWCodec_count; i++) {
			if (ERROR_SUCCESS == key.QueryDWORDValue(hwdec_opt_names[i], dw)) {
				m_bHwCodecs[i] = !!dw;
			}
		}
		if (ERROR_SUCCESS == key.QueryDWORDValue(OPT_HwDecoder, dw)) {
			m_nHwDecoder = (MPCHwDecoder)discard<int>(dw, HWDec_D3D11, 0, HWDec_count-1);
		}
		if (ERROR_SUCCESS == key.QueryStringValue(OPT_HwAdapter, buff, &len) && len > 3) {
			UINT a, b;
			if (swscanf_s(buff, L"%04X:%04X", &a, &b) == 2) {
				m_HwAdapter.VendorId = a;
				m_HwAdapter.DeviceId = b;
			}
		}
		if (ERROR_SUCCESS == key.QueryDWORDValue(OPT_DXVACheck, dw)) {
			m_nDXVACheckCompatibility = dw;
		}
		if (ERROR_SUCCESS == key.QueryDWORDValue(OPT_DisableDXVA_SD, dw)) {
			m_nDXVA_SD = dw;
		}

		for (int i = 0; i < PixFmt_count; i++) {
			CString optname = OPT_SW_prefix;
			optname += GetSWOF(i)->name;
			if (ERROR_SUCCESS == key.QueryDWORDValue(optname, dw)) {
				m_fPixFmts[i] = !!dw;
			}
		}
		if (ERROR_SUCCESS == key.QueryDWORDValue(OPT_SwConvertToRGB, dw)) {
			m_bSwConvertToRGB = !!dw;
		}
		if (ERROR_SUCCESS == key.QueryDWORDValue(OPT_SwRGBLevels, dw)) {
			m_nSwRGBLevels = dw;
		}
	}
	if (ERROR_SUCCESS == key.Open(HKEY_CURRENT_USER, OPT_REGKEY_VCodecs, KEY_READ)) {
		m_nActiveCodecs = 0;
		for (size_t i = 0; i < std::size(vcodecs); i++) {
			DWORD dw = 1;
			key.QueryDWORDValue(vcodecs[i].opt_name, dw);
			if (dw) {
				m_nActiveCodecs |= vcodecs[i].flag;
			}
		}
	}
#else
	int value;
	CProfile& profile = AfxGetProfile();
	profile.ReadInt(OPT_SECTION_VideoDec, OPT_ThreadNumber, m_nThreadNumber, 0, 16);
	if (profile.ReadInt(OPT_SECTION_VideoDec, OPT_ScanType, value)) {
		m_nScanType = (MPC_SCAN_TYPE)value;
	}
	profile.ReadInt(OPT_SECTION_VideoDec, OPT_ARMode, m_nARMode);
	profile.ReadInt(OPT_SECTION_VideoDec, OPT_DiscardMode, m_nDiscardMode);
	for (int i = 0; i < HWCodec_count; i++) {
		profile.ReadBool(OPT_SECTION_VideoDec, hwdec_opt_names[i], m_bHwCodecs[i]);
	}
	if (profile.ReadInt(OPT_SECTION_VideoDec, OPT_HwDecoder, value, 0, HWDec_count - 1)) {
		m_nHwDecoder = (MPCHwDecoder)value;
	}
	if (CStringW str; profile.ReadString(OPT_SECTION_VideoDec, OPT_HwAdapter, str)) {
		UINT a, b;
		if (swscanf_s(str, L"%04X:%04X", &a, &b) == 2) {
			m_HwAdapter.VendorId = a;
			m_HwAdapter.DeviceId = b;
		}
	}
	profile.ReadInt(OPT_SECTION_VideoDec, OPT_DXVACheck, m_nDXVACheckCompatibility);
	profile.ReadInt(OPT_SECTION_VideoDec, OPT_DisableDXVA_SD, m_nDXVA_SD);
	profile.ReadBool(OPT_SECTION_VideoDec, OPT_SwConvertToRGB, m_bSwConvertToRGB);
	profile.ReadInt(OPT_SECTION_VideoDec, OPT_SwRGBLevels, m_nSwRGBLevels);
	for (int i = 0; i < PixFmt_count; i++) {
		CString optname = OPT_SW_prefix;
		optname += GetSWOF(i)->name;
		profile.ReadBool(OPT_SECTION_VideoDec, optname, m_fPixFmts[i]);
	}
#endif

	if (m_nDiscardMode != AVDISCARD_NONREF) {
		m_nDiscardMode = AVDISCARD_DEFAULT;
	}

	m_nDXVACheckCompatibility = std::clamp(m_nDXVACheckCompatibility, 0, 3);

	if (m_nScanType > SCAN_PROGRESSIVE) {
		m_nScanType = SCAN_AUTO;
	}
	if (m_nSwRGBLevels != 1) {
		m_nSwRGBLevels = 0;
	}

#ifdef DEBUG_OR_LOG
	av_log_set_callback(ff_log);
#else
	av_log_set_callback(nullptr);
#endif

	m_FormatConverter.SetOptions(m_nSwRGBLevels);

	HWND hWnd = nullptr;
	EnumWindows(EnumFindProcessWnd, (LPARAM)&hWnd);
	DetectVideoCard(hWnd);

	if (SysVersion::IsWin8orLater() && m_nHwDecoder == HWDec_D3D11) {
		m_pD3D11Decoder = std::make_unique<CD3D11Decoder>(this);
		if (FAILED(m_pD3D11Decoder->Init())) {
			m_pD3D11Decoder.reset();
		}
	}

#ifdef _DEBUG
	// Check codec definition table
	constexpr auto nCodecs               = std::size(ffCodecs);
	constexpr auto nPinTypes             = std::size(sudPinTypesIn);
	constexpr auto nPinTypesUncompressed = std::size(sudPinTypesInUncompressed);
	ASSERT(nCodecs == nPinTypes + nPinTypesUncompressed);
	for (size_t i = 0; i < nPinTypes; i++) {
		ASSERT(ffCodecs[i].clsMinorType == sudPinTypesIn[i].clsMinorType);
	}
	for (size_t i = 0; i < nPinTypesUncompressed ; i++) {
		ASSERT(ffCodecs[nPinTypes + i].clsMinorType == sudPinTypesInUncompressed[i].clsMinorType);
	}
#endif
}

CMPCVideoDecFilter::~CMPCVideoDecFilter()
{
	Cleanup();
	m_pD3D11Decoder.reset();
}

void CMPCVideoDecFilter::DetectVideoCard(HWND hWnd)
{
	m_nPCIVendor         = 0;
	m_nPCIDevice         = 0;
	m_VideoDriverVersion = 0;

	auto pD3D9 = D3D9Helper::Direct3DCreate9();
	if (pD3D9) {
		D3DADAPTER_IDENTIFIER9 AdapID9 = {};
		if (pD3D9->GetAdapterIdentifier(D3D9Helper::GetAdapter(pD3D9, hWnd), 0, &AdapID9) == S_OK) {
			m_nPCIVendor         = AdapID9.VendorId;
			m_nPCIDevice         = AdapID9.DeviceId;
			m_VideoDriverVersion = AdapID9.DriverVersion.QuadPart;
			if (SysVersion::IsWin81orLater() && (m_VideoDriverVersion & 0xffff00000000) == 0 && (m_VideoDriverVersion & 0xffff) == 0) {
				// fix bug in GetAdapterIdentifier()
				m_VideoDriverVersion = (m_VideoDriverVersion & 0xffff000000000000) | ((m_VideoDriverVersion & 0xffff0000) << 16) | 0xffffffff;
			}
			m_strDeviceDescription.Format(L"%S (%04X:%04X)", AdapID9.Description, m_nPCIVendor, m_nPCIDevice);
		}

		pD3D9->Release();
	}
}

REFERENCE_TIME CMPCVideoDecFilter::GetFrameDuration()
{
	if (m_CodecId == AV_CODEC_ID_MPEG2VIDEO || m_CodecId == AV_CODEC_ID_MPEG1VIDEO) {
		if (m_pAVCtx->time_base.num && m_pAVCtx->time_base.den) {
			REFERENCE_TIME frame_duration = (UNITS * m_pAVCtx->time_base.num / m_pAVCtx->time_base.den) * (m_CodecId == AV_CODEC_ID_MPEG2VIDEO ? 2 : 1);
			return frame_duration;
		}
	}

	return m_rtAvrTimePerFrame;
}

void CMPCVideoDecFilter::UpdateFrameTime(REFERENCE_TIME& rtStart, REFERENCE_TIME& rtStop)
{
	auto rtStartInput = rtStart;
	if (rtStart == INVALID_TIME || (m_CodecId == AV_CODEC_ID_H264 && rtStart == m_rtLastStart)) {
		rtStart = m_rtLastStop;
		rtStop = INVALID_TIME;
	}

	if (rtStop == INVALID_TIME || m_bCalculateStopTime) {
		REFERENCE_TIME frame_duration = GetFrameDuration();
		if (m_pFrame && m_pFrame->repeat_pict) {
			frame_duration = frame_duration * 3 / 2;
		}
		rtStop = rtStart + (frame_duration / m_dRate);
	}

	m_rtLastStart = rtStartInput;
	m_rtLastStop = rtStop;
}

void CMPCVideoDecFilter::GetFrameTimeStamp(AVFrame* pFrame, REFERENCE_TIME& rtStart, REFERENCE_TIME& rtStop)
{
	// Reorder B-Frames if needed
	if (m_bReorderBFrame && m_pAVCtx->has_b_frames) {
		rtStart = m_tBFrameDelay[m_nBFramePos].rtStart;
		rtStop  = m_tBFrameDelay[m_nBFramePos].rtStop;
	} else {
		rtStart = pFrame->best_effort_timestamp;
		if (pFrame->duration) {
			rtStop = rtStart + pFrame->duration;
		} else {
			rtStop = INVALID_TIME;
		}
	}
}

bool CMPCVideoDecFilter::AddFrameSideData(IMediaSample* pSample, AVFrame* pFrame)
{
	CheckPointer(pSample, false);
	CheckPointer(pFrame, false);

	CComPtr<IMediaSideData> pMediaSideData;
	if (SUCCEEDED(pSample->QueryInterface(&pMediaSideData))) {
		HRESULT hr = E_FAIL;
		if (auto sd = av_frame_get_side_data(pFrame, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA)) {
			if (sd->size == sizeof(AVMasteringDisplayMetadata)) {
				auto metadata = reinterpret_cast<AVMasteringDisplayMetadata*>(sd->data);
				MediaSideDataHDR hdr = { 0 };

				if (metadata->has_primaries) {
					// export the display primaries in GBR order
					hdr.display_primaries_x[0] = av_q2d(metadata->display_primaries[1][0]);
					hdr.display_primaries_y[0] = av_q2d(metadata->display_primaries[1][1]);
					hdr.display_primaries_x[1] = av_q2d(metadata->display_primaries[2][0]);
					hdr.display_primaries_y[1] = av_q2d(metadata->display_primaries[2][1]);
					hdr.display_primaries_x[2] = av_q2d(metadata->display_primaries[0][0]);
					hdr.display_primaries_y[2] = av_q2d(metadata->display_primaries[0][1]);

					hdr.white_point_x = av_q2d(metadata->white_point[0]);
					hdr.white_point_y = av_q2d(metadata->white_point[1]);
				}

				if (metadata->has_luminance) {
					hdr.max_display_mastering_luminance = av_q2d(metadata->max_luminance);
					hdr.min_display_mastering_luminance = av_q2d(metadata->min_luminance);
				}

				if (metadata->has_primaries || metadata->has_luminance) {
					hr = pMediaSideData->SetSideData(IID_MediaSideDataHDR,
													 reinterpret_cast<const BYTE*>(&hdr),
													 sizeof(hdr));
				}
			} else {
				DLog(L"CMPCVideoDecFilter::AddFrameSideData(): Found HDR data of an unexpected size (%zu)", sd->size);
			}
		} else if (m_FilterInfo.masterDataHDR) {
			hr = pMediaSideData->SetSideData(IID_MediaSideDataHDR,
											 reinterpret_cast<const BYTE*>(m_FilterInfo.masterDataHDR),
											 sizeof(MediaSideDataHDR));
			SAFE_DELETE(m_FilterInfo.masterDataHDR);
		}

		if (AVFrameSideData* sd = av_frame_get_side_data(pFrame, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL)) {
			if (sd->size == sizeof(AVContentLightMetadata)) {
				hr = pMediaSideData->SetSideData(IID_MediaSideDataHDRContentLightLevel, (const BYTE*)sd->data, sd->size);
			} else {
				DLog(L"CMPCVideoDecFilter::AddFrameSideData(): Found HDR Light Level data of an unexpected size (%zu)", sd->size);
			}
		} else if (m_FilterInfo.HDRContentLightLevel) {
			hr = pMediaSideData->SetSideData(IID_MediaSideDataHDRContentLightLevel,
											 reinterpret_cast<const BYTE*>(m_FilterInfo.HDRContentLightLevel),
											 sizeof(MediaSideDataHDRContentLightLevel));
			SAFE_DELETE(m_FilterInfo.HDRContentLightLevel);
		}

		if (AVFrameSideData* sd = av_frame_get_side_data(pFrame, AV_FRAME_DATA_DOVI_METADATA)) {
			auto metadata = reinterpret_cast<AVDOVIMetadata*>(sd->data);
			MediaSideDataDOVIMetadata hdr = {};

			const auto header  = av_dovi_get_header(metadata);
			const auto mapping = av_dovi_get_mapping(metadata);
			const auto color   = av_dovi_get_color(metadata);

#define RPU_HDR(name)   hdr.Header.##name = header->##name;
#define RPU_MAP(name)   hdr.Mapping.##name = mapping->##name;
#define RPU_COLOR(name) hdr.ColorMetadata.##name = color->##name;

			RPU_HDR(rpu_type);
			RPU_HDR(rpu_format);
			RPU_HDR(vdr_rpu_profile);
			RPU_HDR(vdr_rpu_level);
			RPU_HDR(chroma_resampling_explicit_filter_flag);
			RPU_HDR(coef_data_type);
			RPU_HDR(coef_log2_denom);
			RPU_HDR(vdr_rpu_normalized_idc);
			RPU_HDR(bl_video_full_range_flag);
			RPU_HDR(bl_bit_depth);
			RPU_HDR(el_bit_depth);
			RPU_HDR(vdr_bit_depth);
			RPU_HDR(spatial_resampling_filter_flag);
			RPU_HDR(el_spatial_resampling_filter_flag);
			RPU_HDR(disable_residual_flag);

			RPU_MAP(vdr_rpu_id);
			RPU_MAP(mapping_color_space);
			RPU_MAP(mapping_chroma_format_idc);

			for (int i = 0; i < 3; i++) {
				hdr.Mapping.curves[i].num_pivots = mapping->curves[i].num_pivots;
				for (int j = 0; j < AV_DOVI_MAX_PIECES + 1; j++) {
					hdr.Mapping.curves[i].pivots[j] = mapping->curves[i].pivots[j];
				}
				for (int j = 0; j < AV_DOVI_MAX_PIECES; j++) {
					hdr.Mapping.curves[i].mapping_idc[j] = mapping->curves[i].mapping_idc[j];

					// poly
					hdr.Mapping.curves[i].poly_order[j] = mapping->curves[i].poly_order[j];
					hdr.Mapping.curves[i].poly_coef[j][0] = mapping->curves[i].poly_coef[j][0];
					hdr.Mapping.curves[i].poly_coef[j][1] = mapping->curves[i].poly_coef[j][1];
					hdr.Mapping.curves[i].poly_coef[j][2] = mapping->curves[i].poly_coef[j][2];

					// mmr
					hdr.Mapping.curves[i].mmr_order[j] = mapping->curves[i].mmr_order[j];
					hdr.Mapping.curves[i].mmr_constant[j] = mapping->curves[i].mmr_constant[j];
					for (int k = 0; k < 3; k++) {
						for (int l = 0; l < 7; l++) {
							hdr.Mapping.curves[i].mmr_coef[j][k][l] = mapping->curves[i].mmr_coef[j][k][l];
						}
					}
				}
			}

			RPU_MAP(nlq_method_idc);
			RPU_MAP(num_x_partitions);
			RPU_MAP(num_y_partitions);

			for (int i = 0; i < 3; i++) {
				hdr.Mapping.nlq[i].nlq_offset = mapping->nlq[i].nlq_offset;
				hdr.Mapping.nlq[i].vdr_in_max = mapping->nlq[i].vdr_in_max;
				hdr.Mapping.nlq[i].linear_deadzone_slope = mapping->nlq[i].linear_deadzone_slope;
				hdr.Mapping.nlq[i].linear_deadzone_threshold = mapping->nlq[i].linear_deadzone_threshold;
			}

			RPU_COLOR(dm_metadata_id);
			RPU_COLOR(scene_refresh_flag);

			for (int i = 0; i < 9; i++) {
				hdr.ColorMetadata.ycc_to_rgb_matrix[i] = av_q2d(color->ycc_to_rgb_matrix[i]);
				hdr.ColorMetadata.rgb_to_lms_matrix[i] = av_q2d(color->rgb_to_lms_matrix[i]);
			}

			for (int i = 0; i < 3; i++) {
				hdr.ColorMetadata.ycc_to_rgb_offset[i] = av_q2d(color->ycc_to_rgb_offset[i]);
			}

			RPU_COLOR(signal_eotf);
			RPU_COLOR(signal_eotf_param0);
			RPU_COLOR(signal_eotf_param1);
			RPU_COLOR(signal_eotf_param2);
			RPU_COLOR(signal_bit_depth);
			RPU_COLOR(signal_color_space);
			RPU_COLOR(signal_chroma_format);
			RPU_COLOR(signal_full_range_flag);
			RPU_COLOR(source_min_pq);
			RPU_COLOR(source_max_pq);
			RPU_COLOR(source_diagonal);

#undef RPU_HDR
#undef RPU_MAP
#undef RPU_COLOR

			hr = pMediaSideData->SetSideData(IID_MediaSideDataDOVIMetadata,
											 reinterpret_cast<const BYTE*>(&hdr),
											 sizeof(hdr));
		}

		return (hr == S_OK);
	}

	return false;
}

void CMPCVideoDecFilter::GetPictSize(int& width, int& height)
{
	// some codecs can reset the values width/height on initialization
	width  = m_pAVCtx->width  ? m_pAVCtx->width  : m_pAVCtx->coded_width;
	height = m_pAVCtx->height ? m_pAVCtx->height : m_pAVCtx->coded_height;
}

static bool IsFFMPEGEnabled(const FFMPEG_CODECS& ffcodec, const bool FFmpegFilters[VDEC_COUNT])
{
	if (ffcodec.FFMPEGCode < 0 || ffcodec.FFMPEGCode >= VDEC_COUNT) {
		return false;
	}

	return FFmpegFilters[ffcodec.FFMPEGCode];
}

int CMPCVideoDecFilter::FindCodec(const CMediaType* mtIn, BOOL bForced/* = FALSE*/)
{
	m_bUseFFmpeg = false;
	m_hwType = {};

	for (size_t i = 0; i < std::size(ffCodecs); i++) {
		if (mtIn->subtype == *ffCodecs[i].clsMinorType) {
			if (bForced) { // hack
				m_bUseFFmpeg = true;
			}
			else {
#ifdef REGISTER_FILTER
				switch (ffCodecs[i].nFFCodec) {
				case AV_CODEC_ID_FLV1:
				case AV_CODEC_ID_VP6F:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_FLASH) != 0;
					break;
				case AV_CODEC_ID_MPEG4:
					if ((*ffCodecs[i].clsMinorType == MEDIASUBTYPE_DX50) ||	// DivX
						(*ffCodecs[i].clsMinorType == MEDIASUBTYPE_dx50) ||
						(*ffCodecs[i].clsMinorType == MEDIASUBTYPE_DIVX) ||
						(*ffCodecs[i].clsMinorType == MEDIASUBTYPE_divx) ||
						(*ffCodecs[i].clsMinorType == MEDIASUBTYPE_Divx)) {
						m_bUseFFmpeg = (m_nActiveCodecs & CODEC_DIVX) != 0;
					}
					else {
						m_bUseFFmpeg = (m_nActiveCodecs & CODEC_XVID) != 0;	// Xvid/MPEG-4
					}
					break;
				case AV_CODEC_ID_WMV1:
				case AV_CODEC_ID_WMV2:
				case AV_CODEC_ID_WMV3IMAGE:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_WMV) != 0;
					break;
				case AV_CODEC_ID_WMV3:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_WMV) != 0;
					break;
				case AV_CODEC_ID_MSMPEG4V3:
				case AV_CODEC_ID_MSMPEG4V2:
				case AV_CODEC_ID_MSMPEG4V1:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_MSMPEG4) != 0;
					break;
				case AV_CODEC_ID_H264:
					if ((*ffCodecs[i].clsMinorType == MEDIASUBTYPE_MVC1) ||
						(*ffCodecs[i].clsMinorType == MEDIASUBTYPE_AMVC)) {
						m_bUseFFmpeg = (m_nActiveCodecs & CODEC_H264_MVC) != 0;
					}
					else {
						m_bUseFFmpeg = (m_nActiveCodecs & CODEC_H264) != 0;
					}
					break;
				case AV_CODEC_ID_HEVC:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_HEVC) != 0;
					break;
				case AV_CODEC_ID_SPEEDHQ:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_SHQ) != 0;
					break;
				case AV_CODEC_ID_SVQ3:
				case AV_CODEC_ID_SVQ1:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_SVQ3) != 0;
					break;
				case AV_CODEC_ID_H263:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_H263) != 0;
					break;
				case AV_CODEC_ID_DIRAC:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_DIRAC) != 0;
					break;
				case AV_CODEC_ID_DVVIDEO:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_DV) != 0;
					break;
				case AV_CODEC_ID_THEORA:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_THEORA) != 0;
					break;
				case AV_CODEC_ID_VC1:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_VC1) != 0;
					break;
				case AV_CODEC_ID_VC1IMAGE:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_VC1) != 0;
					break;
				case AV_CODEC_ID_AMV:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_AMVV) != 0;
					break;
				case AV_CODEC_ID_LAGARITH:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_LOSSLESS) != 0;
					break;
				case AV_CODEC_ID_VP3:
				case AV_CODEC_ID_VP5:
				case AV_CODEC_ID_VP6:
				case AV_CODEC_ID_VP6A:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_VP356) != 0;
					break;
				case AV_CODEC_ID_VP7:
				case AV_CODEC_ID_VP8:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_VP89) != 0;
					break;
				case AV_CODEC_ID_VP9:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_VP89) != 0;
					break;
				case AV_CODEC_ID_MJPEG:
				case AV_CODEC_ID_MJPEGB:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_MJPEG) != 0;
					break;
				case AV_CODEC_ID_INDEO3:
				case AV_CODEC_ID_INDEO4:
				case AV_CODEC_ID_INDEO5:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_INDEO) != 0;
					break;
				case AV_CODEC_ID_UTVIDEO:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_UTVD) != 0;
					break;
				case AV_CODEC_ID_CSCD:
				case AV_CODEC_ID_TSCC:
				case AV_CODEC_ID_TSCC2:
				case AV_CODEC_ID_VMNC:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_SCREC) != 0;
					break;
				case AV_CODEC_ID_RV10:
				case AV_CODEC_ID_RV20:
				case AV_CODEC_ID_RV30:
				case AV_CODEC_ID_RV40:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_REALV) != 0;
					break;
				case AV_CODEC_ID_MPEG2VIDEO:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_MPEG2) != 0;
					break;
				case AV_CODEC_ID_MPEG1VIDEO:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_MPEG1) != 0;
					break;
				case AV_CODEC_ID_PRORES:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_PRORES) != 0;
					break;
				case AV_CODEC_ID_BINKVIDEO:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_BINKV) != 0;
					break;
				case AV_CODEC_ID_PNG:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_PNG) != 0;
					break;
				case AV_CODEC_ID_CLLC:
				case AV_CODEC_ID_HQ_HQA:
				case AV_CODEC_ID_HQX:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_CANOPUS) != 0;
					break;
				case AV_CODEC_ID_CFHD:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_CINEFORM) != 0;
					break;
				case AV_CODEC_ID_V210:
				case AV_CODEC_ID_V410:
				case AV_CODEC_ID_R210:
				case AV_CODEC_ID_R10K:
				case AV_CODEC_ID_RAWVIDEO:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_UNCOMPRESSED) != 0;
					break;
				case AV_CODEC_ID_DNXHD:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_DNXHD) != 0;
					break;
				case AV_CODEC_ID_CINEPAK:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_CINEPAK) != 0;
					break;
				case AV_CODEC_ID_8BPS:
				case AV_CODEC_ID_QTRLE:
				case AV_CODEC_ID_RPZA:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_QT) != 0;
					break;
				case AV_CODEC_ID_HAP:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_HAP) != 0;
					break;
				case AV_CODEC_ID_AV1:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_AV1) != 0;
					break;
				case AV_CODEC_ID_AVS3:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_AVS3) != 0;
					break;
				case AV_CODEC_ID_VVC:
					m_bUseFFmpeg = (m_nActiveCodecs & CODEC_VVC) != 0;
					break;
				default:
					m_bUseFFmpeg = true;
					break;
				}
#else
				m_bUseFFmpeg = IsFFMPEGEnabled(ffCodecs[i], m_VideoFilters);
#endif
			}

			if (m_bUseFFmpeg && m_bEnableHwDecoding && ffCodecs[i].HwCodec != HWCodec_None) {
				if (m_bHwCodecs[ffCodecs[i].HwCodec]) {
					m_hwType = HwType::DXVA2;
				}
			}

			return m_bUseFFmpeg ? i : -1;
		}
	}

	return -1;
}

void CMPCVideoDecFilter::Cleanup()
{
	CAutoLock cAutoLock(&m_csReceive);

	CleanupFFmpeg();

	m_pMSDKDecoder.reset();
	m_pDXVADecoder.reset();
	m_VideoOutputFormats.clear();

	CleanupD3DResources();

	m_FilterInfo.Clear();
}

void CMPCVideoDecFilter::CleanupD3DResources()
{
	if (m_hDevice != INVALID_HANDLE_VALUE) {
		m_pDeviceManager->CloseDeviceHandle(m_hDevice);
		m_hDevice = INVALID_HANDLE_VALUE;
	}
	m_pDeviceManager.Release();
	m_pDecoderService.Release();
}

void CMPCVideoDecFilter::CleanupDXVAVariables()
{
	m_DXVADecoderGUID = GUID_NULL;
	m_DXVASurfaceFormat = D3DFMT_UNKNOWN;
	ZeroMemory(&m_DXVA2Config, sizeof(m_DXVA2Config));
}

void CMPCVideoDecFilter::CleanupFFmpeg()
{
	m_pAVCodec = nullptr;

	if (m_pParser) {
		av_parser_close(m_pParser);
		m_pParser = nullptr;
	}

	m_pStagingD3D11Texture2D.Release();

	if (m_pAVCtx) {
		av_freep(&m_pAVCtx->hwaccel_context);
		avcodec_free_context(&m_pAVCtx);
	}

	av_buffer_unref(&m_HWDeviceCtx);

	av_frame_free(&m_pFrame);
	av_frame_free(&m_pHWFrame);
	av_packet_free(&m_pPacket);

	m_FormatConverter.Cleanup();

	av_freep(&m_pFFBuffer);
	m_nFFBufferSize = 0;
}

STDMETHODIMP CMPCVideoDecFilter::NonDelegatingQueryInterface(REFIID riid, void** ppv)
{
	return
		QI(IMPCVideoDecFilter)
		QI(IExFilterConfig)
		QI(ISpecifyPropertyPages)
		QI(ISpecifyPropertyPages2)
		__super::NonDelegatingQueryInterface(riid, ppv);
}

HRESULT CMPCVideoDecFilter::CheckInputType(const CMediaType* mtIn)
{
	for (size_t i = 0; i < std::size(sudPinTypesIn); i++) {
		if ((mtIn->majortype == *sudPinTypesIn[i].clsMajorType) &&
				(mtIn->subtype == *sudPinTypesIn[i].clsMinorType)) {
			return S_OK;
		}
	}

	for (size_t i = 0; i < std::size(sudPinTypesInUncompressed); i++) {
		if ((mtIn->majortype == *sudPinTypesInUncompressed[i].clsMajorType) &&
				(mtIn->subtype == *sudPinTypesInUncompressed[i].clsMinorType)) {
			return S_OK;
		}
	}

	return VFW_E_TYPE_NOT_ACCEPTED;
}

HRESULT CMPCVideoDecFilter::CheckTransform(const CMediaType* mtIn, const CMediaType* mtOut)
{
	return CheckInputType(mtIn); // TODO - add check output MediaType
}

HRESULT CMPCVideoDecFilter::SetMediaType(PIN_DIRECTION direction, const CMediaType *pmt)
{
	if (direction == PINDIR_INPUT) {
		if (m_pDXVADecoder) {
			CleanupDXVAVariables();
			CleanupD3DResources();
			m_pDXVADecoder.reset();
		}
		DXVAState::ClearState();
		m_nDecoderMode = MODE_SOFTWARE;
		m_bDXVACompatible = true;

		m_bReinit = true;
		HRESULT hr = InitDecoder(pmt);
		if (FAILED(hr)) {
			return hr;
		}

		DLog(L"CMPCVideoDecFilter::SetMediaType() - PINDIR_INPUT");

		if (m_pOutput->IsConnected() && m_pCurrentMediaType != *pmt) {
			ChangeOutputMediaFormat(2);
		} else {
			BuildOutputFormat();
		}

		m_bDecodingStart       = FALSE;
		m_bDecoderAcceptFormat = FALSE;
		m_pCurrentMediaType    = *pmt;
	} else if (direction == PINDIR_OUTPUT) {
		BITMAPINFOHEADER bihOut;
		if (!ExtractBIH(&m_pOutput->CurrentMediaType(), &bihOut)) {
			return E_FAIL;
		}
		m_FormatConverter.UpdateOutput2(bihOut.biCompression, bihOut.biWidth, bihOut.biHeight);
	}

	return __super::SetMediaType(direction, pmt);
}

bool CMPCVideoDecFilter::IsDXVASupported(const bool bMode)
{
	if (m_CodecId != AV_CODEC_ID_NONE) {
		// Enabled by user ?
		if (bMode) {
			// is the file compatible ?
			if (m_bDXVACompatible) {
				// Does the codec suppport DXVA ?
				for (const auto& mode : DXVAModes) {
					if (m_CodecId == mode.nCodecId) {
						return true;
					}
				}
			}
		}
	}

	return false;
}

HRESULT CMPCVideoDecFilter::FindDecoderConfiguration()
{
	DLog(L"CMPCVideoDecFilter::FindDecoderConfiguration()");

	HRESULT hr = E_FAIL;

	CleanupDXVAVariables();

	if (m_pDecoderService) {
		UINT cDecoderGuids               = 0;
		GUID* pDecoderGuids              = nullptr;
		GUID decoderGuid                 = GUID_NULL;
		BOOL bFoundDXVA2Configuration    = FALSE;
		DXVA2_ConfigPictureDecode config = { 0 };
		D3DFORMAT surfaceFormat          = D3DFMT_UNKNOWN;

		if (SUCCEEDED(hr = m_pDecoderService->GetDecoderDeviceGuids(&cDecoderGuids, &pDecoderGuids)) && cDecoderGuids) {
#ifdef DEBUG_OR_LOG
			CString dbgstr;
			dbgstr.Format(L"    => Enumerating supported DXVA2 modes[%u]:\n", cDecoderGuids);
#endif
			std::vector<GUID> supportedDecoderGuids;
			for (UINT iGuid = 0; iGuid < cDecoderGuids; iGuid++) {
				const auto& guid = pDecoderGuids[iGuid];

				bool supported = IsSupportedDecoderMode(guid);
#ifdef DEBUG_OR_LOG
				dbgstr.AppendFormat(L"        %s", GetDXVAModeStringAndName(guid));
				supported ? dbgstr.Append(L" - supported\n") : dbgstr.Append(L"\n");
#endif
				if (supported) {
					if (guid == DXVA2_ModeH264_E || guid == DXVA2_ModeH264_F) {
						supportedDecoderGuids.insert(supportedDecoderGuids.cbegin(), guid);
					} else {
						supportedDecoderGuids.emplace_back(guid);
					}
				}
			}
#ifdef DEBUG_OR_LOG
			DLog(dbgstr);
#endif

			if (!supportedDecoderGuids.empty()) {
				for (const auto& guid : supportedDecoderGuids) {
					DLog(L"    => Attempt : %s", GetDXVAModeString(guid));

					if (DXVA2_H264_VLD_Intel == guid) {
						const int width_mbs  = m_nSurfaceWidth / 16;
						const int height_mbs = m_nSurfaceWidth / 16;
						const int max_ref_frames_dpb41 = std::min(11, 32768 / (width_mbs * height_mbs));
						if (m_pAVCtx->refs > max_ref_frames_dpb41) {
							DLog(L"    => Too many reference frames for Intel H.264 ClearVideo decoder, skip");
							continue;
						}
					}

					// Find a configuration that we support.
					if (FAILED(hr = FindDXVA2DecoderConfiguration(m_pDecoderService, guid, config, surfaceFormat, bFoundDXVA2Configuration))) {
						break;
					}

					if (bFoundDXVA2Configuration) {
						// Found a good configuration. Save the GUID.
						decoderGuid = guid;
						DLog(L"    => Use : %s", GetDXVAModeString(decoderGuid));
						break;
					}
				}
			}
		}

		if (pDecoderGuids) {
			CoTaskMemFree(pDecoderGuids);
		}
		if (!bFoundDXVA2Configuration) {
			DLog(L"CMPCVideoDecFilter::FindDecoderConfiguration() : no supported format found");
			hr = E_FAIL; // Unable to find a configuration.
		}

		if (SUCCEEDED(hr)) {
			m_DXVA2Config       = config;
			m_DXVADecoderGUID   = decoderGuid;
			m_DXVASurfaceFormat = surfaceFormat;
		}
	}

	return hr;
}

#define H264_CHECK_PROFILE(profile) \
	(((profile) & ~AV_PROFILE_H264_CONSTRAINED) <= AV_PROFILE_H264_HIGH)
#define HEVC_CHECK_PROFILE(profile) \
	((profile) <= AV_PROFILE_HEVC_MAIN_10)
#define VP9_CHECK_PROFILE(profile) \
	((profile) == AV_PROFILE_VP9_0 || (profile) == AV_PROFILE_VP9_2)
#define AV1_CHECK_PROFILE(profile) \
	((profile) == AV_PROFILE_AV1_MAIN)

bool CMPCVideoDecFilter::CheckDXVACompatible(const enum AVCodecID codec, const enum AVPixelFormat pix_fmt, const int profile)
{
	switch (codec) {
		case AV_CODEC_ID_MPEG2VIDEO:
			if (pix_fmt != AV_PIX_FMT_YUV420P && pix_fmt != AV_PIX_FMT_YUVJ420P) {
				return false;
			}
			break;
		case AV_CODEC_ID_H264:
			if (pix_fmt != AV_PIX_FMT_YUV420P && pix_fmt != AV_PIX_FMT_YUVJ420P) {
				return false;
			}
			if (profile != AV_PROFILE_UNKNOWN && !H264_CHECK_PROFILE(profile)) {
				return false;
			}
			break;
		case AV_CODEC_ID_HEVC:
			if (profile == AV_PROFILE_HEVC_REXT && (m_hwType == HwType::D3D11 || m_hwType == HwType::NVDEC || m_hwType == HwType::D3D11CopyBack)) {
				return true;
			}

			if (pix_fmt != AV_PIX_FMT_YUV420P && pix_fmt != AV_PIX_FMT_YUVJ420P && pix_fmt != AV_PIX_FMT_YUV420P10) {
				return false;
			}
			if (profile != AV_PROFILE_UNKNOWN && !HEVC_CHECK_PROFILE(profile)) {
				return false;
			}
			break;
		case AV_CODEC_ID_VP9:
			if (pix_fmt != AV_PIX_FMT_YUV420P && pix_fmt != AV_PIX_FMT_YUVJ420P && pix_fmt != AV_PIX_FMT_YUV420P10) {
				return false;
			}
			if (profile != AV_PROFILE_UNKNOWN && !VP9_CHECK_PROFILE(profile)) {
				return false;
			}
			break;
		case AV_CODEC_ID_AV1:
			if (pix_fmt != AV_PIX_FMT_YUV420P && pix_fmt != AV_PIX_FMT_YUVJ420P && pix_fmt != AV_PIX_FMT_YUV420P10) {
				return false;
			}
			if (profile != AV_PROFILE_UNKNOWN && !AV1_CHECK_PROFILE(profile)) {
				return false;
			}
			break;
		case AV_CODEC_ID_WMV3:
		case AV_CODEC_ID_VC1:
			if (profile == AV_PROFILE_VC1_COMPLEX) {
				return false;
			}
	}

	return true;
}

HRESULT CMPCVideoDecFilter::InitDecoder(const CMediaType* pmt)
{
	CAutoLock cLock(&m_csInitDec);

	DLog(L"CMPCVideoDecFilter::InitDecoder()");

	CheckPointer(pmt, VFW_E_TYPE_NOT_ACCEPTED);

	bool bMediaTypeChanged = (m_pCurrentMediaType != *pmt);
	const bool bReinit = (m_pAVCtx != nullptr);

	int64_t x264_build = -1;
	if (m_CodecId == AV_CODEC_ID_H264 && bReinit && !bMediaTypeChanged) {
		int64_t val = -1;
		if (av_opt_get_int(m_pAVCtx->priv_data, "x264_build", 0, &val) >= 0) {
			x264_build = val;
		}
	}

	for (;;) {
		CleanupFFmpeg();

		// Prevent connection to the video decoder - need to support decoding of uncompressed video (v210, V410, Y8, I420)
		CComPtr<IBaseFilter> pFilter = GetFilterFromPin(m_pInput->GetConnected());
		if (pFilter && IsVideoDecoder(pFilter, true)) {
			return VFW_E_TYPE_NOT_ACCEPTED;
		}

		if (bMediaTypeChanged) {
			ExtractAvgTimePerFrame(pmt, m_rtAvrTimePerFrame);
			int wout, hout;
			ExtractDim(pmt, wout, hout, m_nARX, m_nARY);
			UNREFERENCED_PARAMETER(wout);
			UNREFERENCED_PARAMETER(hout);
		}

		m_bMVC_Output_TopBottom = FALSE;
		if (pmt->subtype == MEDIASUBTYPE_AMVC || pmt->subtype == MEDIASUBTYPE_MVC1) {
			if (!m_pMSDKDecoder) {
				m_pMSDKDecoder = std::make_unique<CMSDKDecoder>(this);
			}
			HRESULT hr = m_pMSDKDecoder->InitDecoder(pmt);

			if (hr != S_OK) {
				m_pMSDKDecoder.reset();
			}

			if (m_pMSDKDecoder) {
				m_bMVC_Output_TopBottom = m_iMvcOutputMode == MVC_OUTPUT_TopBottom;
				m_pMSDKDecoder->SetOutputMode(m_iMvcOutputMode, m_bMvcSwapLR);
				return S_OK;
			}

			return VFW_E_TYPE_NOT_ACCEPTED;
		}

		if (bMediaTypeChanged) {
			const int nNewCodec = FindCodec(pmt, bReinit);
			if (nNewCodec == -1) {
				return VFW_E_TYPE_NOT_ACCEPTED;
			}
			m_CodecId = ffCodecs[nNewCodec].nFFCodec;

			if (m_hwType == HwType::DXVA2) {
				if (m_pD3D11Decoder) {
					m_hwType = HwType::D3D11;
				} else {
					switch (m_nHwDecoder) {
						case HWDec_D3D11cb: m_hwType = HwType::D3D11CopyBack; break;
						case HWDec_D3D12cb: m_hwType = HwType::D3D12CopyBack; break;
						case HWDec_NVDEC:   m_hwType = HwType::NVDEC;         break;
					}
				}
			}
		}

		if (m_CodecId == AV_CODEC_ID_AV1 && m_hwType != HwType::None) {
			m_pAVCodec = avcodec_find_decoder_by_name("av1");
		} else {
			m_pAVCodec = avcodec_find_decoder(m_CodecId);
		}
		CheckPointer(m_pAVCodec, VFW_E_UNSUPPORTED_VIDEO);

		if (bMediaTypeChanged && (m_hwType == HwType::D3D11CopyBack || m_hwType == HwType::D3D12CopyBack || m_hwType == HwType::NVDEC)) {
			auto hwDeviceType = m_hwType == HwType::D3D11CopyBack ? AV_HWDEVICE_TYPE_D3D11VA :
								(m_hwType == HwType::D3D12CopyBack ? AV_HWDEVICE_TYPE_D3D12VA : AV_HWDEVICE_TYPE_CUDA);
			m_HWPixFmt = AV_PIX_FMT_NONE;
			for (int i = 0;; i++) {
				auto config = avcodec_get_hw_config(m_pAVCodec, i);
				if (!config) {
					DLog(L"CMPCVideoDecFilter::InitDecoder() : %s decoder initialization FAILED.",
						 m_hwType == HwType::D3D11CopyBack ? L"D3D11-copyback" :
						 (m_hwType == HwType::D3D12CopyBack ? L"D3D12-copyback" : L"NVDEC"));
					m_hwType = HwType::None;
					break;
				}
				if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
						config->device_type == hwDeviceType) {
					m_HWPixFmt = config->pix_fmt;
					break;
				}
			}
		}

		if (bMediaTypeChanged) {
			const CLSID clsidInput = GetCLSID(m_pInput->GetConnected());
			const BOOL bNotTrustSourceTimeStamp = (clsidInput == GUIDFromCString(L"{A2E7EDBB-DCDD-4C32-A2A9-0CFBBE6154B4}") // Daum PotPlayer's MKV Source
												   || clsidInput == CLSID_WMAsfReader); // WM ASF Reader

			m_bCalculateStopTime = (m_CodecId == AV_CODEC_ID_H264 ||
									m_CodecId == AV_CODEC_ID_DIRAC ||
									m_CodecId == AV_CODEC_ID_AVS3 ||
									(m_CodecId == AV_CODEC_ID_MPEG4 && pmt->formattype == FORMAT_MPEG2Video)
									|| bNotTrustSourceTimeStamp);

			m_bRVDropBFrameTimings = (m_CodecId == AV_CODEC_ID_RV10 || m_CodecId == AV_CODEC_ID_RV20 || m_CodecId == AV_CODEC_ID_RV30 || m_CodecId == AV_CODEC_ID_RV40);

			auto ReadSourceHeader = [&]() {
				if (m_dwSYNC != 0) {
					return;
				}
				m_dwSYNC = -1;

				CString fn;

				BeginEnumFilters(m_pGraph, pEF, pBF) {
					CComQIPtr<IFileSourceFilter> pFSF(pBF);
					if (pFSF) {
						LPOLESTR pFN = nullptr;
						AM_MEDIA_TYPE mt;
						if (SUCCEEDED(pFSF->GetCurFile(&pFN, &mt)) && pFN && *pFN) {
							fn = CString(pFN);
							CoTaskMemFree(pFN);
						}
						break;
					}
				}
				EndEnumFilters

					if (!fn.IsEmpty() && ::PathFileExistsW(fn)) {
						CFile f;
						CFileException fileException;
						if (!f.Open(fn, CFile::modeRead | CFile::typeBinary | CFile::shareDenyNone, &fileException)) {
							DLog(L"CMPCVideoDecFilter::InitDecoder() : Can't open file '%s', error = %d", fn, fileException.m_cause);
							return;
						}

						f.Read(&m_dwSYNC, sizeof(m_dwSYNC));
						f.Seek(4, CFile::current);
						f.Read(&m_dwSYNC2, sizeof(m_dwSYNC2));
						f.Close();
					}
			};

			auto IsAVI = [&]() {
				ReadSourceHeader();
				return (m_dwSYNC == FCC('RIFF') && (m_dwSYNC2 == FCC('AVI ') || m_dwSYNC == FCC('AVIX') || m_dwSYNC == FCC('AMV ')));
			};
			auto IsOGG = [&]() {
				ReadSourceHeader();
				return (m_dwSYNC == FCC('OggS'));
			};

			// Enable B-Frame reorder
			m_bReorderBFrame = !(clsidInput == __uuidof(CMpegSourceFilter) || clsidInput == __uuidof(CMpegSplitterFilter))
								&& !(m_pAVCodec->capabilities & (AV_CODEC_CAP_DELAY | AV_CODEC_CAP_FRAME_THREADS))
								&& !(m_CodecId == AV_CODEC_ID_MPEG1VIDEO || m_CodecId == AV_CODEC_ID_MPEG2VIDEO)
								|| (m_CodecId == AV_CODEC_ID_MPEG4 && pmt->formattype != FORMAT_MPEG2Video)
								|| clsidInput == __uuidof(CAviSourceFilter) || clsidInput == __uuidof(CAviSplitterFilter)
								|| clsidInput == __uuidof(COggSourceFilter) || clsidInput == __uuidof(COggSplitterFilter)
								|| (m_CodecId == AV_CODEC_ID_HEVC && (clsidInput == __uuidof(CFLVSourceFilter) || clsidInput == __uuidof(CFLVSplitterFilter))
								|| IsAVI() || IsOGG());
			if (!m_bReorderBFrame && (m_CodecId == AV_CODEC_ID_VC1 || m_CodecId == AV_CODEC_ID_WMV3)
					&& !(clsidInput == __uuidof(CMpegSourceFilter) || clsidInput == __uuidof(CMpegSplitterFilter))) {
				m_bReorderBFrame = true;
			}
		}

		m_pAVCtx = avcodec_alloc_context3(m_pAVCodec);
		CheckPointer(m_pAVCtx, E_POINTER);

		if (m_CodecId == AV_CODEC_ID_MPEG2VIDEO
				|| m_CodecId == AV_CODEC_ID_MPEG1VIDEO
				|| m_CodecId == AV_CODEC_ID_AVS3
				|| pmt->subtype == MEDIASUBTYPE_H264
				|| pmt->subtype == MEDIASUBTYPE_h264
				|| pmt->subtype == MEDIASUBTYPE_X264
				|| pmt->subtype == MEDIASUBTYPE_x264
				|| pmt->subtype == MEDIASUBTYPE_H264_bis
				|| pmt->subtype == MEDIASUBTYPE_HEVC) {
			m_pParser = av_parser_init(m_CodecId);
		}

		SetThreadCount();

		m_pFrame = av_frame_alloc();
		CheckPointer(m_pFrame, E_POINTER);
		m_pHWFrame = av_frame_alloc();
		CheckPointer(m_pHWFrame, E_POINTER);
		m_pPacket = av_packet_alloc();
		CheckPointer(m_pPacket, E_POINTER);

		BITMAPINFOHEADER* pBMI = nullptr;
		bool bInterlacedFieldPerSample = false;
		m_inputDxvaExtFormat.value = 0;
		if (pmt->formattype == FORMAT_VideoInfo) {
			VIDEOINFOHEADER* vih = (VIDEOINFOHEADER*)pmt->pbFormat;
			pBMI = &vih->bmiHeader;
		} else if (pmt->formattype == FORMAT_VideoInfo2) {
			VIDEOINFOHEADER2* vih2 = (VIDEOINFOHEADER2*)pmt->pbFormat;
			pBMI = &vih2->bmiHeader;
			bInterlacedFieldPerSample = vih2->dwInterlaceFlags & AMINTERLACE_IsInterlaced && vih2->dwInterlaceFlags & AMINTERLACE_1FieldPerSample;
			if (vih2->dwControlFlags & (AMCONTROL_USED | AMCONTROL_COLORINFO_PRESENT)) {
				m_inputDxvaExtFormat.value = vih2->dwControlFlags & 0xFFFFFF00;
			}
		} else if (pmt->formattype == FORMAT_MPEGVideo) {
			MPEG1VIDEOINFO* mpgv = (MPEG1VIDEOINFO*)pmt->pbFormat;
			pBMI = &mpgv->hdr.bmiHeader;
		} else if (pmt->formattype == FORMAT_MPEG2Video) {
			VIDEOINFOHEADER2& vih2 = ((MPEG2VIDEOINFO*)pmt->pbFormat)->hdr;
			pBMI = &vih2.bmiHeader;
			bInterlacedFieldPerSample = vih2.dwInterlaceFlags & AMINTERLACE_IsInterlaced && vih2.dwInterlaceFlags & AMINTERLACE_1FieldPerSample;
			if (vih2.dwControlFlags & (AMCONTROL_USED | AMCONTROL_COLORINFO_PRESENT)) {
				m_inputDxvaExtFormat.value = vih2.dwControlFlags & 0xFFFFFF00;
			}
		} else {
			return VFW_E_INVALIDMEDIATYPE;
		}

		if (m_CodecId == AV_CODEC_ID_MPEG2VIDEO && bInterlacedFieldPerSample && m_nPCIVendor == PCIV_ATI
				&& (m_hwType == HwType::DXVA2 || m_hwType == HwType::D3D11)) {
			m_hwType = HwType::None;
		}

		if (bMediaTypeChanged) {
			m_bWaitKeyFrame = m_CodecId == AV_CODEC_ID_VC1
							  || m_CodecId == AV_CODEC_ID_VC1IMAGE
							  || m_CodecId == AV_CODEC_ID_WMV3
							  || m_CodecId == AV_CODEC_ID_WMV3IMAGE
							  || m_CodecId == AV_CODEC_ID_MPEG2VIDEO
							  || m_CodecId == AV_CODEC_ID_RV30
							  || m_CodecId == AV_CODEC_ID_RV40
							  || m_CodecId == AV_CODEC_ID_VP3
							  || m_CodecId == AV_CODEC_ID_THEORA
							  || m_CodecId == AV_CODEC_ID_MPEG4
							  || m_CodecId == AV_CODEC_ID_VVC;
		}

		m_pAVCtx->codec_id = m_CodecId;
		m_pAVCtx->codec_tag = pBMI->biCompression ? pBMI->biCompression : pmt->subtype.Data1;
		if (m_pAVCtx->codec_tag == MAKEFOURCC('m', 'p', 'g', '2')) {
			m_pAVCtx->codec_tag = MAKEFOURCC('M', 'P', 'E', 'G');
		}
		m_pAVCtx->coded_width = pBMI->biWidth;
		m_pAVCtx->coded_height = abs(pBMI->biHeight);
		m_pAVCtx->bits_per_coded_sample = pBMI->biBitCount;
		m_pAVCtx->workaround_bugs = FF_BUG_AUTODETECT;
		m_pAVCtx->skip_frame = (AVDiscard)m_nDiscardMode;
		m_pAVCtx->opaque = this;

		if (m_hwType != HwType::None) {
			if (IsDXVASupported(m_hwType == HwType::D3D11)) {
				m_pD3D11Decoder->AdditionaDecoderInit(m_pAVCtx);
			} else if (IsDXVASupported(m_hwType == HwType::DXVA2)) {
				m_pAVCtx->hwaccel_context = (dxva_context*)av_mallocz(sizeof(dxva_context));
				m_pAVCtx->get_format = av_get_format;
				m_pAVCtx->get_buffer2 = av_get_buffer;
				m_pAVCtx->slice_flags |= SLICE_FLAG_ALLOW_FIELD;
			} else if ((m_hwType == HwType::D3D11CopyBack || m_hwType == HwType::D3D12CopyBack || m_hwType == HwType::NVDEC)
					   && m_HWPixFmt != AV_PIX_FMT_NONE) {
				CStringA device("0");
				if ((m_hwType == HwType::D3D11CopyBack || m_hwType == HwType::D3D12CopyBack) && m_HwAdapter.DeviceId && m_HwAdapter.VendorId) {
					std::list<DXGI_ADAPTER_DESC> dxgi_adapters;
					if (SUCCEEDED(GetDxgiAdapters(dxgi_adapters))) {
						unsigned n = 0;
						for (const auto& dxgi_adapter : dxgi_adapters) {
							if (dxgi_adapter.DeviceId == m_HwAdapter.DeviceId && dxgi_adapter.VendorId == m_HwAdapter.VendorId) {
								device.Format("%u", n);
								break;
							}
							++n;
						}
					}
				}
				auto type = m_hwType == HwType::D3D11CopyBack ? AV_HWDEVICE_TYPE_D3D11VA :
							(m_hwType == HwType::D3D12CopyBack ? AV_HWDEVICE_TYPE_D3D12VA : AV_HWDEVICE_TYPE_CUDA);
				if (av_hwdevice_ctx_create(&m_HWDeviceCtx, type, device.GetString(), nullptr, 0) < 0) {
					m_HWPixFmt = AV_PIX_FMT_NONE;
					m_hwType = HwType::None;
				} else {
					m_pAVCtx->get_format = av_get_format;
					m_pAVCtx->hw_device_ctx = av_buffer_ref(m_HWDeviceCtx);
				}
			}
		}

		AllocExtradata(pmt);

		AVDictionary* options = nullptr;
		if (m_CodecId == AV_CODEC_ID_H264 && x264_build != -1) {
			av_dict_set_int(&options, "x264_build", x264_build, 0);
		}

		avcodec_lock;
		m_bInInit = TRUE;
		const int ret = avcodec_open2(m_pAVCtx, m_pAVCodec, &options);
		m_bInInit = FALSE;
		avcodec_unlock;

		if (options) {
			av_dict_free(&options);
		}

		if (ret < 0) {
			DLog("CMPCVideoDecFilter::InitDecoder() avcodec_open2 return '%s'", AVError2Str(ret));
			return VFW_E_INVALIDMEDIATYPE;
		}

		if (m_CodecId == AV_CODEC_ID_HEVC && m_pAVCtx->pix_fmt == AV_PIX_FMT_NONE && m_pAVCtx->extradata_size > 0) {
			vc_params_t params;
			if (HEVCParser::ParseHEVCDecoderConfigurationRecord(m_pAVCtx->extradata, m_pAVCtx->extradata_size, params, false)) {
				m_pAVCtx->profile = params.profile;
				if (m_pAVCtx->profile == AV_PROFILE_HEVC_MAIN_10) {
					m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV420P10LE;
				} else if (m_pAVCtx->profile == AV_PROFILE_HEVC_MAIN) {
					m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV420P;
				}
			}
		}

		FillAVCodecProps(m_pAVCtx, pBMI);

		if (pFilter) {
			CComPtr<IExFilterInfo> pIExFilterInfo;
			if (SUCCEEDED(pFilter->QueryInterface(&pIExFilterInfo))) {
				if (m_CodecId == AV_CODEC_ID_H264) {
					int value = 0;
					if (SUCCEEDED(pIExFilterInfo->GetPropertyInt("VIDEO_DELAY", &value))) {
						m_pAVCtx->has_b_frames = value;
					}
				}

				if (bMediaTypeChanged) {
					m_FilterInfo.Clear();
					int value = 0;
					if (SUCCEEDED(pIExFilterInfo->GetPropertyInt("VIDEO_PROFILE", &value))) {
						m_FilterInfo.profile = value;
					}
					if (SUCCEEDED(pIExFilterInfo->GetPropertyInt("VIDEO_PIXEL_FORMAT", &value))) {
						m_FilterInfo.pix_fmt = value;
					}

					if (SUCCEEDED(pIExFilterInfo->GetPropertyInt("VIDEO_INTERLACED", &value))) {
						m_FilterInfo.interlaced = value;
					}

					if (!m_bReorderBFrame && (m_CodecId == AV_CODEC_ID_H264 || m_CodecId == AV_CODEC_ID_HEVC)) {
						if (SUCCEEDED(pIExFilterInfo->GetPropertyInt("VIDEO_FLAG_ONLY_DTS", &value))
							&& value == 1) {
							m_bReorderBFrame = true;
						}
					}

					unsigned size = 0;
					LPVOID pData = nullptr;
					if (SUCCEEDED(pIExFilterInfo->GetPropertyBin("VIDEO_COLOR_SPACE", &pData, &size))) {
						if (size == sizeof(ColorSpace)) {
							auto const colorSpace = (ColorSpace*)pData;

							if (colorSpace->MatrixCoefficients < AVCOL_SPC_NB
									&& colorSpace->MatrixCoefficients != AVCOL_SPC_RGB
									&& colorSpace->MatrixCoefficients != AVCOL_SPC_UNSPECIFIED
									&& colorSpace->MatrixCoefficients != AVCOL_SPC_RESERVED) {
								m_FilterInfo.colorspace = colorSpace->MatrixCoefficients;
							}

							if (colorSpace->Primaries < AVCOL_PRI_NB
									&& colorSpace->Primaries != AVCOL_PRI_RESERVED0
									&& colorSpace->Primaries != AVCOL_PRI_UNSPECIFIED
									&& colorSpace->Primaries != AVCOL_PRI_RESERVED) {
								m_FilterInfo.color_primaries = colorSpace->Primaries;
							}

							if (colorSpace->TransferCharacteristics < AVCOL_TRC_NB
									&& colorSpace->TransferCharacteristics != AVCOL_TRC_RESERVED0
									&& colorSpace->TransferCharacteristics != AVCOL_TRC_UNSPECIFIED
									&& colorSpace->TransferCharacteristics != AVCOL_TRC_RESERVED) {
								m_FilterInfo.color_trc = colorSpace->TransferCharacteristics;
							}

							if (colorSpace->ChromaLocation < AVCHROMA_LOC_NB && colorSpace->ChromaLocation != AVCHROMA_LOC_UNSPECIFIED) {
								m_FilterInfo.chroma_sample_location = colorSpace->ChromaLocation;
							}

							if (colorSpace->Range < AVCOL_RANGE_NB && colorSpace->Range != AVCOL_RANGE_UNSPECIFIED) {
								m_FilterInfo.color_range = colorSpace->Range;
							}
						}
						LocalFree(pData);
					}
					if (SUCCEEDED(pIExFilterInfo->GetPropertyBin("HDR_MASTERING_METADATA", &pData, &size))) {
						if (size == sizeof(MediaSideDataHDR)) {
							m_FilterInfo.masterDataHDR = DNew MediaSideDataHDR;
							memcpy(m_FilterInfo.masterDataHDR, pData, size);
						}
						LocalFree(pData);
					}
					if (SUCCEEDED(pIExFilterInfo->GetPropertyBin("HDR_CONTENT_LIGHT_LEVEL", &pData, &size))) {
						if (size == sizeof(MediaSideDataHDRContentLightLevel)) {
							m_FilterInfo.HDRContentLightLevel = DNew MediaSideDataHDRContentLightLevel;
							memcpy(m_FilterInfo.HDRContentLightLevel, pData, size);
						}
						LocalFree(pData);
					}
					if (SUCCEEDED(pIExFilterInfo->GetPropertyBin("PALETTE", &pData, &size))) {
						if (size == sizeof(m_Palette)) {
							m_bHasPalette = true;
							memcpy(m_Palette, pData, size);
						}
						LocalFree(pData);
					}
				}
			}
		}

		if (m_FilterInfo.profile != -1) {
			m_pAVCtx->profile = m_FilterInfo.profile;
		}
		if (m_FilterInfo.pix_fmt != AV_PIX_FMT_NONE) {
			m_pAVCtx->pix_fmt = (AVPixelFormat)m_FilterInfo.pix_fmt;
		}

		m_nAlign = 16;
		if (m_CodecId == AV_CODEC_ID_MPEG2VIDEO) {
			m_nAlign <<= 1;
		} else if (m_CodecId == AV_CODEC_ID_HEVC || m_CodecId == AV_CODEC_ID_AV1) {
			m_nAlign = 128;
		}

		m_nSurfaceWidth = FFALIGN(m_pAVCtx->coded_width, m_nAlign);
		m_nSurfaceHeight = FFALIGN(m_pAVCtx->coded_height, m_nAlign);

		const int depth = GetLumaBits(m_pAVCtx->pix_fmt);
		m_bHighBitdepth = (depth == 10) && ((m_CodecId == AV_CODEC_ID_HEVC && (m_pAVCtx->profile == AV_PROFILE_HEVC_MAIN_10 || m_pAVCtx->profile == AV_PROFILE_HEVC_REXT))
											|| (m_CodecId == AV_CODEC_ID_VP9 && m_pAVCtx->profile == AV_PROFILE_VP9_2)
											|| (m_CodecId == AV_CODEC_ID_AV1 && m_pAVCtx->profile == AV_PROFILE_AV1_MAIN));

		m_dxvaExtFormat = GetDXVA2ExtendedFormat(m_pAVCtx, m_pFrame);
		m_dxva_pix_fmt = m_pAVCtx->pix_fmt;

		if (bMediaTypeChanged && IsDXVASupported(m_hwType != HwType::None)) {
			for (;;) {
				m_bDXVACompatible = false;

				int w, h;
				GetPictSize(w, h);
				if (!DXVACheckFramesize(w, h, m_nPCIVendor, m_nPCIDevice, m_VideoDriverVersion)) { // check frame size
					break;
				}

				if (m_CodecId == AV_CODEC_ID_H264) {
					// check "Disable DXVA for SD (H.264)" option
					if (m_nDXVA_SD && std::max(m_nSurfaceWidth, m_nSurfaceHeight) <= 1024 && std::min(m_nSurfaceWidth, m_nSurfaceHeight) <= 576) {
						break;
					}

					if (m_hwType != HwType::NVDEC && m_nDXVACheckCompatibility < 3) {
						const int nCompat = FFH264CheckCompatibility(m_nSurfaceWidth, m_nSurfaceHeight, m_pAVCtx, m_nPCIVendor, m_nPCIDevice, m_VideoDriverVersion);

						if ((nCompat & DXVA_PROFILE_HIGHER_THAN_HIGH) || (nCompat & DXVA_HIGH_BIT)) { // DXVA unsupported
							break;
						}

						if (nCompat) {
							bool bDXVACompatible = false;
							switch (m_nDXVACheckCompatibility) {
								case 1:
									bDXVACompatible = (nCompat == DXVA_UNSUPPORTED_LEVEL);
									break;
								case 2:
									bDXVACompatible = (nCompat == DXVA_TOO_MANY_REF_FRAMES);
									break;
							}
							if (!bDXVACompatible) {
								break;
							}
						}
					}
				} else if (!CheckDXVACompatible(m_CodecId, m_pAVCtx->pix_fmt, m_pAVCtx->profile)) {
					break;
				}

				m_bDXVACompatible = true;
				break;
			};

			if (!m_bDXVACompatible) {
				bMediaTypeChanged = false;
				m_hwType = HwType::None;
				continue;
			}
		}

		break;
	}

	av_frame_unref(m_pFrame);

	if (UseDXVA2() && m_pDXVADecoder) {
		m_pDXVADecoder->FillHWContext();
	}

	if (bMediaTypeChanged && IsDXVASupported(m_hwType == HwType::D3D11)) {
		m_pD3D11Decoder->PostInitDecoder(m_pAVCtx);
	}

	m_bFailDXVA2Decode = m_bFailD3D11Decode = FALSE;

	return S_OK;
}

static const VIDEO_OUTPUT_FORMATS DXVA_NV12 = { &MEDIASUBTYPE_NV12, FCC('dxva'), 12, 1 };
static const VIDEO_OUTPUT_FORMATS DXVA_P010 = { &MEDIASUBTYPE_P010, FCC('dxva'), 24, 2 };

// 420 12 bit
static const VIDEO_OUTPUT_FORMATS DXVA_P016 = { &MEDIASUBTYPE_P016, FCC('dxva'), 24, 2 };
// 422 8/10/12 bit
static const VIDEO_OUTPUT_FORMATS DXVA_YUY2 = { &MEDIASUBTYPE_YUY2, FCC('dxva'), 16, 2 };
static const VIDEO_OUTPUT_FORMATS DXVA_Y210 = { &MEDIASUBTYPE_Y210, FCC('dxva'), 32, 2 };
static const VIDEO_OUTPUT_FORMATS DXVA_Y216 = { &MEDIASUBTYPE_Y216, FCC('dxva'), 32, 2 };
// 444 8/10/12 bit
static const VIDEO_OUTPUT_FORMATS DXVA_AYUV = { &MEDIASUBTYPE_AYUV, FCC('dxva'), 32, 4 };
static const VIDEO_OUTPUT_FORMATS DXVA_Y410 = { &MEDIASUBTYPE_Y410, FCC('dxva'), 32, 4 };
static const VIDEO_OUTPUT_FORMATS DXVA_Y416 = { &MEDIASUBTYPE_Y416, FCC('dxva'), 64, 8 };

void CMPCVideoDecFilter::BuildOutputFormat()
{
	m_VideoOutputFormats.clear();

	// === New swscaler options
	int nSwIndex[PixFmt_count] = { 0 };
	int nSwCount = 0;

	const enum AVPixelFormat pix_fmt = m_pMSDKDecoder ? AV_PIX_FMT_NV12 : (m_pAVCtx->sw_pix_fmt != AV_PIX_FMT_NONE ? m_pAVCtx->sw_pix_fmt : m_pAVCtx->pix_fmt);
	const AVPixFmtDescriptor* av_pfdesc = av_pix_fmt_desc_get(pix_fmt);

	if (pix_fmt != AV_PIX_FMT_NONE) {
		if (av_pfdesc) {
			int lumabits = av_pfdesc->comp[0].depth;

			const MPCPixelFormat* OutList = nullptr;

			if (m_bSwConvertToRGB || av_pfdesc->flags & (AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_PAL)) {
				if (lumabits <= 10) {
					OutList = RGB_8;
				}
				else {
					OutList = RGB_16;
				}
			}
			else if (av_pfdesc->nb_components <= 2) { // greyscale formats with and without alfa channel
				if (lumabits <= 8) {
					OutList = YUV420_8;
				}
				else if (lumabits <= 10) {
					OutList = YUV420_10;
				}
				else {
					OutList = YUV420_16;
				}
			}
			else if (av_pfdesc->nb_components >= 3) {
				if (av_pfdesc->log2_chroma_w == 1 && av_pfdesc->log2_chroma_h == 1) { // 4:2:0
					if (lumabits <= 8) {
						OutList = YUV420_8;
					}
					else if (lumabits <= 10) {
						OutList = YUV420_10;
					}
					else {
						OutList = YUV420_16;
					}
				}
				else if (av_pfdesc->log2_chroma_w == 1 && av_pfdesc->log2_chroma_h == 0) { // 4:2:2
					if (lumabits <= 8) {
						OutList = YUV422_8;
					}
					else if (lumabits <= 10) {
						OutList = YUV422_10;
					}
					else {
						OutList = YUV422_16;
					}
				}
				else if (av_pfdesc->log2_chroma_w == 0 && av_pfdesc->log2_chroma_h == 0) { // 4:4:4
					if (lumabits <= 8) {
						OutList = YUV444_8;
					}
					else if (lumabits <= 10) {
						OutList = YUV444_10;
					}
					else {
						OutList = YUV444_16;
					}
				}
			}

			if (OutList == nullptr) {
				OutList = YUV420_8;
			}

			for (int i = 0; OutList[i] != PixFmt_None; i++) {
				int index = OutList[i];
				if (m_fPixFmts[index]) {
					nSwIndex[nSwCount++] = index;
				}
			}
		}
	}

	if (m_bSwConvertToRGB || av_pfdesc->flags & (AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_PAL)) {
		if (!m_fPixFmts[PixFmt_RGB32] || nSwCount == 0) {
			// if RGB32 has not been added yet, then add it to the end of the list
			nSwIndex[nSwCount++] = PixFmt_RGB32;
		}
	}
	else {
		if (!m_fPixFmts[PixFmt_YUY2] || nSwCount == 0) {
			// if YUY2 has not been added yet, then add it to the end of the list
			nSwIndex[nSwCount++] = PixFmt_YUY2;
		}
	}

	int OutputCount = m_bUseFFmpeg ? nSwCount : 0;
	if (IsDXVASupported(m_hwType == HwType::DXVA2 || m_hwType == HwType::D3D11)) {
		OutputCount++;
	}
	m_VideoOutputFormats.reserve(OutputCount);

	int nPos = 0;
	if (IsDXVASupported(m_hwType == HwType::DXVA2 || m_hwType == HwType::D3D11)) {
		if (m_hwType == HwType::D3D11 && m_CodecId == AV_CODEC_ID_HEVC && m_pAVCtx->profile == AV_PROFILE_HEVC_REXT) {
			switch (pix_fmt) {
				case AV_PIX_FMT_YUV420P12: m_VideoOutputFormats.push_back(m_nPCIVendor == PCIV_nVidia ? DXVA_P010 : DXVA_P016); break;
				case AV_PIX_FMT_YUV422P:   m_VideoOutputFormats.push_back(DXVA_YUY2); break;
				case AV_PIX_FMT_YUV422P10: m_VideoOutputFormats.push_back(DXVA_Y210); break;
				case AV_PIX_FMT_YUV422P12: m_VideoOutputFormats.push_back(DXVA_Y216); break;
				case AV_PIX_FMT_YUV444P:   m_VideoOutputFormats.push_back(DXVA_AYUV); break;
				case AV_PIX_FMT_YUV444P10: m_VideoOutputFormats.push_back(DXVA_Y410); break;
				case AV_PIX_FMT_YUV444P12: m_VideoOutputFormats.push_back(DXVA_Y416); break;
			}
		} else if (m_bHighBitdepth) {
			m_VideoOutputFormats.push_back(DXVA_P010);
		} else {
			m_VideoOutputFormats.push_back(DXVA_NV12);
		}
	}

	// Software rendering
	if (m_bUseFFmpeg) {
		for (int i = 0; i < nSwCount; i++) {
			const SW_OUT_FMT* swof = GetSWOF(nSwIndex[i]);
			m_VideoOutputFormats.emplace_back(
				VIDEO_OUTPUT_FORMATS{ swof->subtype, swof->biCompression, (UINT)swof->bpp, (UINT)swof->codedbytes }
			);
		}
	}
	ASSERT(OutputCount == m_VideoOutputFormats.size());
}

void CMPCVideoDecFilter::GetOutputFormats(int& nNumber, VIDEO_OUTPUT_FORMATS** ppFormats)
{
	nNumber    = m_VideoOutputFormats.size();
	*ppFormats = m_VideoOutputFormats.size() ? m_VideoOutputFormats.data() : nullptr;
}

static void ReconstructH264Extra(BYTE *extra, unsigned& extralen, int NALSize)
{
	CH264Nalu Nalu;
	Nalu.SetBuffer(extra, extralen, NALSize);
	bool pps_present = false;
	bool bNeedReconstruct = false;

	while (Nalu.ReadNext()) {
		const NALU_TYPE nalu_type = Nalu.GetType();
		if (nalu_type == NALU_TYPE_PPS) {
			pps_present = true;
		} else if (nalu_type == NALU_TYPE_SPS) {
			bNeedReconstruct = pps_present;
			break;
		}
	}

	if (bNeedReconstruct) {
		BYTE* dst = (uint8_t *)av_mallocz(extralen);
		if (!dst) {
			return;
		}
		size_t dstlen = 0;
		Nalu.SetBuffer(extra, extralen, NALSize);
		while (Nalu.ReadNext()) {
			if (Nalu.GetType() == NALU_TYPE_SPS) {
				memcpy(dst, Nalu.GetNALBuffer(), Nalu.GetLength());
				dstlen += Nalu.GetLength();
				break;
			}
		}

		Nalu.SetBuffer(extra, extralen, NALSize);
		while (Nalu.ReadNext()) {
			if (Nalu.GetType() != NALU_TYPE_SPS) {
				memcpy(dst + dstlen, Nalu.GetNALBuffer(), Nalu.GetLength());
				dstlen += Nalu.GetLength();
			}
		}

		memcpy(extra, dst, extralen);
		av_freep(&dst);
	}
}

void CMPCVideoDecFilter::AllocExtradata(const CMediaType* pmt)
{
	// code from LAV ...
	// Process Extradata
	BYTE *extra = nullptr;
	unsigned extralen = 0;
	getExtraData((const BYTE *)pmt->Format(), pmt->FormatType(), pmt->FormatLength(), nullptr, &extralen);

	BOOL bH264avc = FALSE;
	if (pmt->formattype == FORMAT_MPEG2Video && (m_pAVCtx->codec_tag == MAKEFOURCC('a','v','c','1') || m_pAVCtx->codec_tag == MAKEFOURCC('A','V','C','1') || m_pAVCtx->codec_tag == MAKEFOURCC('C','C','V','1'))) {
		DLog(L"CMPCVideoDecFilter::AllocExtradata() : processing AVC1 extradata of %d bytes", extralen);
		// Reconstruct AVC1 extradata format
		MPEG2VIDEOINFO *mp2vi = (MPEG2VIDEOINFO *)pmt->Format();
		extralen += 7;
		extra = (uint8_t *)av_mallocz(extralen + AV_INPUT_BUFFER_PADDING_SIZE);
		extra[0] = 1;
		extra[1] = (BYTE)mp2vi->dwProfile;
		extra[2] = 0;
		extra[3] = (BYTE)mp2vi->dwLevel;
		extra[4] = (BYTE)(mp2vi->dwFlags ? mp2vi->dwFlags : 4) - 1;

		// only process extradata if available
		uint8_t ps_count = 0;
		if (extralen > 7) {
			// Actually copy the metadata into our new buffer
			unsigned actual_len;
			getExtraData((const BYTE *)pmt->Format(), pmt->FormatType(), pmt->FormatLength(), extra + 6, &actual_len);
			ReconstructH264Extra(extra + 6, actual_len, 2);

			// Count the number of SPS/PPS in them and set the length
			// We'll put them all into one block and add a second block with 0 elements afterwards
			// The parsing logic does not care what type they are, it just expects 2 blocks.
			BYTE *p = extra + 6, *end = extra + 6 + actual_len;
			BOOL bSPS = FALSE, bPPS = FALSE;
			while (p + 1 < end) {
				unsigned len = (((unsigned)p[0] << 8) | p[1]) + 2;
				if (p + len > end) {
					break;
				}
				if ((p[2] & 0x1F) == 7)
					bSPS = TRUE;
				if ((p[2] & 0x1F) == 8)
					bPPS = TRUE;
				ps_count++;
				p += len;
			}
		}
		extra[5] = ps_count;
		extra[extralen - 1] = 0;

		bH264avc = TRUE;
	} else if (extralen > 0) {
		DLog(L"CMPCVideoDecFilter::AllocExtradata() : processing extradata of %d bytes", extralen);
		// Just copy extradata for other formats
		extra = (uint8_t *)av_mallocz(extralen + AV_INPUT_BUFFER_PADDING_SIZE);
		getExtraData((const BYTE *)pmt->Format(), pmt->FormatType(), pmt->FormatLength(), extra, nullptr);

		if (m_CodecId == AV_CODEC_ID_H264) {
			ReconstructH264Extra(extra, extralen, 0);
		} else if (m_CodecId == AV_CODEC_ID_HEVC) {
			// try Reconstruct NAL units sequence into NAL Units in Byte-Stream Format
			BYTE* dst   = nullptr;
			int dst_len = 0;
			BOOL vps_present = FALSE, sps_present = FALSE, pps_present = FALSE;
			CH265Nalu Nalu;
			Nalu.SetBuffer(extra, extralen, 2);
			while (!(vps_present && sps_present && pps_present)
					&& Nalu.ReadNext()) {
				const NALU_TYPE nalu_type = Nalu.GetType();
				switch (nalu_type) {
					case NALU_TYPE_HEVC_VPS:
					case NALU_TYPE_HEVC_SPS:
					case NALU_TYPE_HEVC_PPS:
						if (nalu_type == NALU_TYPE_HEVC_VPS) {
							if (vps_present) continue;
							vc_params_t params = { 0 };
							if (!HEVCParser::ParseVideoParameterSet(Nalu.GetDataBuffer() + 2, Nalu.GetDataLength() - 2, params)) {
								break;
							}
							vps_present = TRUE;
						} else if (nalu_type == NALU_TYPE_HEVC_SPS) {
							if (sps_present) continue;
							vc_params_t params = { 0 };
							if (!HEVCParser::ParseSequenceParameterSet(Nalu.GetDataBuffer() + 2, Nalu.GetDataLength() - 2, params)) {
								break;
							}
							sps_present = TRUE;
						} else if (nalu_type == NALU_TYPE_HEVC_PPS) {
							if (pps_present) continue;
							pps_present = TRUE;
						}

						static const BYTE start_code[]    = { 0, 0, 1 };
						static const UINT start_code_size = sizeof(start_code);

						dst = (BYTE *)av_realloc_f(dst, dst_len + Nalu.GetDataLength() + start_code_size + AV_INPUT_BUFFER_PADDING_SIZE, 1);
						memcpy(dst + dst_len, start_code, start_code_size);
						dst_len += start_code_size;
						memcpy(dst + dst_len, Nalu.GetDataBuffer(), Nalu.GetDataLength());
						dst_len += Nalu.GetDataLength();
				}
			}

			if (vps_present && sps_present && pps_present) {
				av_freep(&extra);
				extra    = dst;
				extralen = dst_len;
			} else {
				av_freep(&dst);
			}
		} else if (m_CodecId == AV_CODEC_ID_VP9) {
			// use code from LAV
			// read custom vpcC headers
			if (extralen >= 16
					&& AV_RB32(extra) == 'vpcC' && AV_RB8(extra + 4) == 1) {
				m_pAVCtx->profile = AV_RB8(extra + 8);
				m_pAVCtx->color_primaries = (AVColorPrimaries)AV_RB8(extra + 11);
				m_pAVCtx->color_trc = (AVColorTransferCharacteristic)AV_RB8(extra + 12);
				m_pAVCtx->colorspace = (AVColorSpace)AV_RB8(extra + 13);

				m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV420P;
				int bitdepth = AV_RB8(extra + 10) >> 4;

				enum VPX_CHROMA_SUBSAMPLING {
					VPX_SUBSAMPLING_420_VERTICAL             = 0,
					VPX_SUBSAMPLING_420_COLLOCATED_WITH_LUMA = 1,
					VPX_SUBSAMPLING_422                      = 2,
					VPX_SUBSAMPLING_444                      = 3,
					VPX_SUBSAMPLING_440                      = 4,
				};
				auto vpx_chroma_subsampling = static_cast<VPX_CHROMA_SUBSAMPLING>((AV_RB8(extra + 10) >> 1) & 0x7);

				switch (bitdepth) {
					case 8:
						if (m_pAVCtx->colorspace == AVCOL_SPC_RGB) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_GBRP;
						} else if (vpx_chroma_subsampling == VPX_SUBSAMPLING_422) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV422P;
						} else if (vpx_chroma_subsampling == VPX_SUBSAMPLING_444) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV444P;
						} else if (vpx_chroma_subsampling == VPX_SUBSAMPLING_440) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV440P;
						}
						break;
					case 10:
						if (m_pAVCtx->colorspace == AVCOL_SPC_RGB) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_GBRP10;
						} else if (vpx_chroma_subsampling == VPX_SUBSAMPLING_422) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV422P10;
						} else if (vpx_chroma_subsampling == VPX_SUBSAMPLING_444) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV444P10;
						} else if (vpx_chroma_subsampling == VPX_SUBSAMPLING_440) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV440P10;
						} else if (m_pAVCtx->profile == AV_PROFILE_VP9_2) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV420P10;
						} else if (m_pAVCtx->profile == AV_PROFILE_VP9_3) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV422P10;
						}
						break;
					case 12:
						if (m_pAVCtx->colorspace == AVCOL_SPC_RGB) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_GBRP12;
						} else if (vpx_chroma_subsampling == VPX_SUBSAMPLING_422) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV422P12;
						} else if (vpx_chroma_subsampling == VPX_SUBSAMPLING_444) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV444P12;
						} else if (vpx_chroma_subsampling == VPX_SUBSAMPLING_440) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV440P12;
						} else if (m_pAVCtx->profile == AV_PROFILE_VP9_2) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV420P12;
						} else if (m_pAVCtx->profile == AV_PROFILE_VP9_3) {
							m_pAVCtx->pix_fmt = AV_PIX_FMT_YUV422P12;
						}
						break;
				}

				av_freep(&extra);
				extralen = 0;
			}
		} else if (m_CodecId == AV_CODEC_ID_AV1) {
			if (extralen >= 4 && AV_RB8(extra) == 0x81) {
				CGolombBuffer gb(extra + 1, extralen - 1);
				const unsigned seq_profile = gb.BitRead(3);
				gb.BitRead(5); // seq_level_idx
				gb.BitRead(1); // seq_tier
				const unsigned high_bitdepth = gb.BitRead(1);
				const unsigned twelve_bit = gb.BitRead(1);
				const unsigned monochrome = gb.BitRead(1);
				const unsigned chroma_subsampling_x = gb.BitRead(1);
				const unsigned chroma_subsampling_y = gb.BitRead(1);

				enum Dav1dPixelLayout {
					DAV1D_PIXEL_LAYOUT_I400, ///< monochrome
					DAV1D_PIXEL_LAYOUT_I420, ///< 4:2:0 planar
					DAV1D_PIXEL_LAYOUT_I422, ///< 4:2:2 planar
					DAV1D_PIXEL_LAYOUT_I444, ///< 4:4:4 planar
				};

				enum Dav1dBitDepth {
					DAV1D_8BIT,
					DAV1D_10BIT,
					DAV1D_12BIT,
				};

				static const enum AVPixelFormat pix_fmts[][3] = {
					{ AV_PIX_FMT_GRAY8,   AV_PIX_FMT_GRAY10,    AV_PIX_FMT_GRAY12    },
					{ AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV420P12 },
					{ AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV422P12 },
					{ AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV444P10, AV_PIX_FMT_YUV444P12 },
				};

				Dav1dBitDepth bitdepth_index = DAV1D_8BIT;
				Dav1dPixelLayout layout = DAV1D_PIXEL_LAYOUT_I420;

				switch (seq_profile) {
					case AV_PROFILE_AV1_MAIN:
						bitdepth_index = high_bitdepth ? DAV1D_10BIT : DAV1D_8BIT;
						layout = monochrome ? DAV1D_PIXEL_LAYOUT_I400 : DAV1D_PIXEL_LAYOUT_I420;
						break;
					case AV_PROFILE_AV1_HIGH:
						bitdepth_index = high_bitdepth ? DAV1D_10BIT : DAV1D_8BIT;
						layout = DAV1D_PIXEL_LAYOUT_I444;
						break;
					case AV_PROFILE_AV1_PROFESSIONAL:
						bitdepth_index = high_bitdepth ? (twelve_bit ? DAV1D_12BIT : DAV1D_10BIT) : DAV1D_8BIT;
						if (monochrome) {
							layout = DAV1D_PIXEL_LAYOUT_I400;
						} else {
							if (bitdepth_index < DAV1D_12BIT) {
								layout = DAV1D_PIXEL_LAYOUT_I422;
							} else {
								if (chroma_subsampling_x && chroma_subsampling_y) {
									layout = DAV1D_PIXEL_LAYOUT_I420;
								} else if (chroma_subsampling_x && !chroma_subsampling_y) {
									layout = DAV1D_PIXEL_LAYOUT_I422;
								} else if (!chroma_subsampling_x && !chroma_subsampling_y) {
									layout = DAV1D_PIXEL_LAYOUT_I444;
								}
							}
						}
						break;
				}

				m_pAVCtx->profile = seq_profile;
				m_pAVCtx->pix_fmt = pix_fmts[layout][bitdepth_index];

				av_freep(&extra);
				extralen = 0;
			}
		}
	}

	// Hack to discard invalid MP4 metadata with AnnexB style video
	if (m_CodecId == AV_CODEC_ID_H264 && !bH264avc && extra && extra[0] == 1) {
		av_freep(&extra);
		extralen = 0;
	}

	m_pAVCtx->extradata      = extra;
	m_pAVCtx->extradata_size = (int)extralen;
}

HRESULT CMPCVideoDecFilter::CompleteConnect(PIN_DIRECTION direction, IPin* pReceivePin)
{
	if (direction == PINDIR_OUTPUT) {
		DLog(L"CMPCVideoDecFilter::CompleteConnect() - PINDIR_OUTPUT");

		m_OutputFilterClsid = GetCLSID(pReceivePin);

		HRESULT hr = S_OK;
		if (IsDXVASupported(m_hwType == HwType::DXVA2 || m_hwType == HwType::D3D11)) {
			const auto& mt = m_pOutput->CurrentMediaType();
			if (!(m_hwType == HwType::D3D11 && m_CodecId == AV_CODEC_ID_HEVC && m_pAVCtx->profile == AV_PROFILE_HEVC_REXT)
					&& mt.subtype != MEDIASUBTYPE_NV12 && mt.subtype != MEDIASUBTYPE_P010) {
				DLog(L"CMPCVideoDecFilter::CompleteConnect() - wrong output media type '%s' for H/W decoding, fallback to software decoding", GetGUIDString(mt.subtype));

				CleanupDXVAVariables();
				CleanupD3DResources();
				m_pDXVADecoder.reset();
				m_nDecoderMode = MODE_SOFTWARE;
				DXVAState::ClearState();
				m_hwType = HwType::None;

				hr = VFW_E_TYPE_NOT_ACCEPTED;
			} else if (IsDXVASupported(m_hwType == HwType::D3D11)) {
				if (m_OutputFilterClsid == CLSID_madVR && (mt.subtype == MEDIASUBTYPE_Y410 || mt.subtype == MEDIASUBTYPE_Y416)) {
					DLog(L"CMPCVideoDecFilter::CompleteConnect() - madVR don't support media type '%s' for D3D11 H/W decoding, fallback to software decoding", GetGUIDString(mt.subtype));
					hr = E_FAIL;
				} else {
					hr = m_pD3D11Decoder->PostConnect(m_pAVCtx, pReceivePin);
					if (SUCCEEDED(hr)) {
						m_nDecoderMode = MODE_D3D11;
						DXVAState::SetActiveState(GUID_NULL, L"D3D11 Native");

						auto adapterDesc = m_pD3D11Decoder->GetAdapterDesc();
						m_nPCIVendor = adapterDesc->VendorId;
						m_nPCIDevice = adapterDesc->DeviceId;
						m_strDeviceDescription.Format(L"%s (%04X:%04X)", adapterDesc->Description, m_nPCIVendor, m_nPCIDevice);
					}
				}

				if (FAILED(hr)) {
					m_nDecoderMode = MODE_SOFTWARE;
					DXVAState::ClearState();
					m_hwType = HwType::DXVA2;
				}
			} else if (IsDXVASupported(m_hwType == HwType::DXVA2)) {
				for (;;) {
					hr = ConfigureDXVA2(pReceivePin);
					if (FAILED(hr)) {
						DLog(L"CMPCVideoDecFilter::CompleteConnect() : ConfigureDXVA2() - FAILED (0x%08x)", hr);
						break;
					}

					CComPtr<IDirectXVideoDecoderService> pDXVA2Service;
					hr = m_pDeviceManager->GetVideoService(m_hDevice, IID_PPV_ARGS(&pDXVA2Service));
					if (FAILED(hr)) {
						DLog(L"CMPCVideoDecFilter::CompleteConnect() : IDirect3DDeviceManager9::GetVideoService() - FAILED (0x%08x)", hr);
						break;
					}
					if (!pDXVA2Service) {
						break;
					}

					const UINT numSurfaces = std::max(m_DXVA2Config.ConfigMinRenderTargetBuffCount, 1ui16);
					LPDIRECT3DSURFACE9 pSurfaces[DXVA2_MAX_SURFACES] = {};
					hr = pDXVA2Service->CreateSurface(
						m_nSurfaceWidth,
						m_nSurfaceHeight,
						numSurfaces - 1,
						m_VideoDesc.Format,
						D3DPOOL_DEFAULT,
						0,
						DXVA2_VideoDecoderRenderTarget,
						pSurfaces,
						nullptr);
					if (FAILED(hr)) {
						DLog(L"CMPCVideoDecFilter::CompleteConnect() : IDirectXVideoDecoderService::CreateSurface() - FAILED (0x%08x)", hr);
						break;
					}

					CComPtr<IDirectXVideoDecoder> pDirectXVideoDec;
					hr = m_pDecoderService->CreateVideoDecoder(m_DXVADecoderGUID, &m_VideoDesc, &m_DXVA2Config, pSurfaces, numSurfaces, &pDirectXVideoDec);
					if (FAILED(hr)) {
						DLog(L"CMPCVideoDecFilter::CompleteConnect() : IDirectXVideoDecoder::CreateVideoDecoder() - FAILED (0x%08x)", hr);
					}

					for (UINT i = 0; i < numSurfaces; i++) {
						SAFE_RELEASE(pSurfaces[i]);
					}

					if (SUCCEEDED(hr)) {
						hr = SetEVRForDXVA2(pReceivePin);
					}

					if (SUCCEEDED(hr)) {
						m_nDecoderMode = MODE_DXVA2;
					}

					break;
				}

				if (FAILED(hr)) {
					CleanupDXVAVariables();
					CleanupD3DResources();
					m_pDXVADecoder.reset();
					m_nDecoderMode = MODE_SOFTWARE;
					DXVAState::ClearState();
					m_hwType = HwType::None;
				}
			}
		}

		if (m_nDecoderMode == MODE_SOFTWARE) {
			if (!m_bUseFFmpeg) {
				return VFW_E_INVALIDMEDIATYPE;
			}

			if (FAILED(hr)) {
				HRESULT hr2 = InitDecoder(&m_pCurrentMediaType);
				if (FAILED(hr2)) {
					return hr2;
				}

				if (hr != VFW_E_TYPE_NOT_ACCEPTED) {
					ChangeOutputMediaFormat(2);
				}
			}
		}

		DetectVideoCard_EVR(pReceivePin);

		if (m_pMSDKDecoder) {
			m_MVC_Base_View_R_flag = FALSE;
			BeginEnumFilters(m_pGraph, pEF, pBF) {
				if (CComQIPtr<IPropertyBag> pPB = pBF.p) {
					CComVariant var;
					if (SUCCEEDED(pPB->Read(L"STEREOSCOPIC3DMODE", &var, nullptr)) && var.vt == VT_BSTR) {
						CString mode(var.bstrVal); mode.MakeLower();
						m_MVC_Base_View_R_flag = mode == L"mvc_rl";
						break;
					}
				}
			}
			EndEnumFilters;
		}

		m_bReinit = false;
	}

	return __super::CompleteConnect(direction, pReceivePin);
}

HRESULT CMPCVideoDecFilter::DecideBufferSize(IMemAllocator* pAllocator, ALLOCATOR_PROPERTIES* pProperties)
{
	if (UseDXVA2() || UseD3D11()) {
		if (m_pInput->IsConnected() == FALSE) {
			return E_UNEXPECTED;
		}

		pProperties->cBuffers = 22;

		HRESULT hr = S_OK;
		ALLOCATOR_PROPERTIES Actual;

		if (FAILED(hr = pAllocator->SetProperties(pProperties, &Actual))) {
			return hr;
		}

		return pProperties->cBuffers > Actual.cBuffers || pProperties->cbBuffer > Actual.cbBuffer
			   ? E_FAIL
			   : NOERROR;
	} else {
		return __super::DecideBufferSize(pAllocator, pProperties);
	}
}

HRESULT CMPCVideoDecFilter::BeginFlush()
{
	return __super::BeginFlush();
}

HRESULT CMPCVideoDecFilter::EndFlush()
{
	CAutoLock cAutoLock(&m_csReceive);
	HRESULT hr =  __super::EndFlush();

	if (m_pAVCtx && avcodec_is_open(m_pAVCtx)) {
		avcodec_flush_buffers(m_pAVCtx);
	}

	return hr;
}

HRESULT CMPCVideoDecFilter::NewSegment(REFERENCE_TIME rtStart, REFERENCE_TIME rtStop, double dRate)
{
	DLog(L"CMPCVideoDecFilter::NewSegment()");

	CAutoLock cAutoLock(&m_csReceive);

	if (m_pAVCtx) {
		avcodec_flush_buffers(m_pAVCtx);
	}

	if (m_pParser) {
		av_parser_close(m_pParser);
		m_pParser = av_parser_init(m_CodecId);
	}

	if (m_pMSDKDecoder) {
		m_pMSDKDecoder->Flush();
	}

	m_dRate	= dRate;

	m_bWaitingForKeyFrame = TRUE;

	m_rtStartCache = INVALID_TIME;

	m_rtLastStart = INVALID_TIME;
	m_rtLastStop = 0;

	if (m_bReorderBFrame) {
		m_nBFramePos = 0;
		m_tBFrameDelay[0].rtStart = m_tBFrameDelay[0].rtStop = INVALID_TIME;
		m_tBFrameDelay[1].rtStart = m_tBFrameDelay[1].rtStop = INVALID_TIME;
	}

	if (m_bDecodingStart && m_pAVCtx) {
		if (m_CodecId == AV_CODEC_ID_H264 || m_CodecId == AV_CODEC_ID_MPEG2VIDEO) {
			InitDecoder(&m_pCurrentMediaType);
		}

		if (UseDXVA2() && m_CodecId == AV_CODEC_ID_H264 && m_nPCIVendor == PCIV_ATI) {
			HRESULT hr = ReinitDXVA2Decoder();
			if (FAILED(hr)) {
				return hr;
			}
		}
	}

	return __super::NewSegment(rtStart, rtStop, dRate);
}

HRESULT CMPCVideoDecFilter::EndOfStream()
{
	CAutoLock cAutoLock(&m_csReceive);
	m_pMSDKDecoder
		? m_pMSDKDecoder->EndOfStream()
		: Decode(nullptr, 0, INVALID_TIME, INVALID_TIME);

	return __super::EndOfStream();
}

HRESULT CMPCVideoDecFilter::BreakConnect(PIN_DIRECTION dir)
{
	if (dir == PINDIR_INPUT) {
		Cleanup();
	}

	return __super::BreakConnect(dir);
}

void CMPCVideoDecFilter::SetTypeSpecificFlags(IMediaSample* pMS)
{
	if (CComQIPtr<IMediaSample2> pMS2 = pMS) {
		AM_SAMPLE2_PROPERTIES props;
		if (SUCCEEDED(pMS2->GetProperties(sizeof(props), (BYTE*)&props))) {
			props.dwTypeSpecificFlags &= ~0x7f;

			switch (m_nScanType) {
				case SCAN_AUTO :
					if (m_CodecId == AV_CODEC_ID_HEVC) {
						props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_WEAVE;
					} else if (m_FilterInfo.interlaced != -1) {
						switch (m_FilterInfo.interlaced) {
							case 0 :
								props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_WEAVE;
								break;
							case 1 :
								props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_FIELD1FIRST;
								break;
						}
					} else {
						if (!(m_pFrame->flags & AV_FRAME_FLAG_INTERLACED)) {
							props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_WEAVE;
						}
						if (m_pFrame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST) {
							props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_FIELD1FIRST;
						}
						if (m_pFrame->repeat_pict) {
							props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_REPEAT_FIELD;
						}
					}
					break;
				case SCAN_PROGRESSIVE :
					props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_WEAVE;
					break;
				case SCAN_TOPFIELD :
					props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_FIELD1FIRST;
					break;
			}

			switch (m_pFrame->pict_type) {
				case AV_PICTURE_TYPE_I :
				case AV_PICTURE_TYPE_SI :
					props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_I_SAMPLE;
					break;
				case AV_PICTURE_TYPE_P :
				case AV_PICTURE_TYPE_SP :
					props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_P_SAMPLE;
					break;
				default :
					props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_B_SAMPLE;
					break;
			}

			pMS2->SetProperties(sizeof(props), (BYTE*)&props);
		}
	}
}

// from LAVVideo
DXVA2_ExtendedFormat CMPCVideoDecFilter::GetDXVA2ExtendedFormat(const AVCodecContext *ctx, const AVFrame *frame)
{
	DXVA2_ExtendedFormat fmt = {};

	if (m_FormatConverter.GetOutPixFormat() == PixFmt_RGB32 || m_FormatConverter.GetOutPixFormat() == PixFmt_RGB48) {
		return fmt;
	}

	auto color_primaries = (m_FilterInfo.color_primaries != -1) ? m_FilterInfo.color_primaries : ctx->color_primaries;
	auto colorspace = (m_FilterInfo.colorspace != -1) ? m_FilterInfo.colorspace : ctx->colorspace;
	auto color_trc = (m_FilterInfo.color_trc != -1) ? m_FilterInfo.color_trc : ctx->color_trc;
	auto chroma_sample_location = (m_FilterInfo.chroma_sample_location != -1) ? m_FilterInfo.chroma_sample_location : ctx->chroma_sample_location;
	auto color_range = (m_FilterInfo.color_range != -1) ? m_FilterInfo.color_range : ctx->color_range;

	// Color Primaries
	switch(color_primaries) {
		case AVCOL_PRI_BT709:
			fmt.VideoPrimaries = DXVA2_VideoPrimaries_BT709;
			break;
		case AVCOL_PRI_BT470M:
			fmt.VideoPrimaries = DXVA2_VideoPrimaries_BT470_2_SysM;
			break;
		case AVCOL_PRI_BT470BG:
			fmt.VideoPrimaries = DXVA2_VideoPrimaries_BT470_2_SysBG;
			break;
		case AVCOL_PRI_SMPTE170M:
			fmt.VideoPrimaries = DXVA2_VideoPrimaries_SMPTE170M;
			break;
		case AVCOL_PRI_SMPTE240M:
			fmt.VideoPrimaries = DXVA2_VideoPrimaries_SMPTE240M;
			break;
		// Values from newer Windows SDK (MediaFoundation)
		case AVCOL_PRI_BT2020:
			fmt.VideoPrimaries = MFVideoPrimaries_BT2020;
			break;
		case AVCOL_PRI_SMPTE428:
			// XYZ
			fmt.VideoPrimaries = MFVideoPrimaries_XYZ;
			break;
		case AVCOL_PRI_SMPTE431:
			// DCI-P3
			fmt.VideoPrimaries = MFVideoPrimaries_DCI_P3;
			break;
		case AVCOL_PRI_UNSPECIFIED:
			if (m_CodecId == AV_CODEC_ID_RAWVIDEO && m_inputDxvaExtFormat.VideoPrimaries) {
				fmt.VideoPrimaries = m_inputDxvaExtFormat.VideoPrimaries;
			}
	}

	// Color Space / Transfer Matrix
	switch (colorspace) {
		case AVCOL_SPC_BT709:
			fmt.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT709;
			break;
		case AVCOL_SPC_BT470BG:
		case AVCOL_SPC_SMPTE170M:
			fmt.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT601;
			break;
		case AVCOL_SPC_SMPTE240M:
			fmt.VideoTransferMatrix = DXVA2_VideoTransferMatrix_SMPTE240M;
			break;
		// Values from newer Windows SDK (MediaFoundation)
		case AVCOL_SPC_BT2020_CL:
		case AVCOL_SPC_BT2020_NCL:
			if (m_OutputFilterClsid == CLSID_EnhancedVideoRenderer) {
				// Hack to get the same result on different video adapters after EVR mixer.
				fmt.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT709;
			} else {
				fmt.VideoTransferMatrix = MFVideoTransferMatrix_BT2020_10;
			}
			break;
		// Custom values, not official standard, but understood by madVR, YCGCO understood by EVR-CP
		case AVCOL_SPC_FCC:
			fmt.VideoTransferMatrix = VIDEOTRANSFERMATRIX_FCC;
			break;
		case AVCOL_SPC_YCGCO:
			fmt.VideoTransferMatrix = VIDEOTRANSFERMATRIX_YCgCo;
			break;
		case AVCOL_SPC_UNSPECIFIED:
			if (m_CodecId == AV_CODEC_ID_RAWVIDEO && m_inputDxvaExtFormat.VideoTransferMatrix) {
				fmt.VideoTransferMatrix = m_inputDxvaExtFormat.VideoTransferMatrix;
			}
			else if (ctx->width <= 1024 && ctx->height <= 576) { // SD
				fmt.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT601;
			} else { // HD
				fmt.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT709;
			}
	}

	// Color Transfer Function
	switch(color_trc) {
		case AVCOL_TRC_BT709:
		case AVCOL_TRC_SMPTE170M:
		case AVCOL_TRC_BT2020_10:
		case AVCOL_TRC_BT2020_12:
			fmt.VideoTransferFunction = DXVA2_VideoTransFunc_709;
			break;
		case AVCOL_TRC_GAMMA22:
			fmt.VideoTransferFunction = DXVA2_VideoTransFunc_22;
			break;
		case AVCOL_TRC_GAMMA28:
			fmt.VideoTransferFunction = DXVA2_VideoTransFunc_28;
			break;
		case AVCOL_TRC_SMPTE240M:
			fmt.VideoTransferFunction = DXVA2_VideoTransFunc_240M;
			break;
		case AVCOL_TRC_LINEAR:
			fmt.VideoTransferFunction = DXVA2_VideoTransFunc_10;
			break;
		// Values from newer Windows SDK (MediaFoundation)
		case AVCOL_TRC_LOG:
			fmt.VideoTransferFunction = MFVideoTransFunc_Log_100;
			break;
		case AVCOL_TRC_LOG_SQRT:
			fmt.VideoTransferFunction = MFVideoTransFunc_Log_316;
			break;
		case AVCOL_TRC_SMPTEST2084:
			fmt.VideoTransferFunction = MFVideoTransFunc_2084;
			break;
		case AVCOL_TRC_ARIB_STD_B67:
			fmt.VideoTransferFunction = MFVideoTransFunc_HLG;
			break;
		case AVCOL_TRC_UNSPECIFIED:
			if (m_CodecId == AV_CODEC_ID_RAWVIDEO && m_inputDxvaExtFormat.VideoTransferFunction) {
				fmt.VideoTransferFunction = m_inputDxvaExtFormat.VideoTransferFunction;
			}
	}

	if (frame->format == AV_PIX_FMT_XYZ12LE || frame->format == AV_PIX_FMT_XYZ12BE) {
		fmt.VideoPrimaries = DXVA2_VideoPrimaries_BT709;
	}

	// Chroma location
	switch(chroma_sample_location) {
		case AVCHROMA_LOC_LEFT:
			fmt.VideoChromaSubsampling = DXVA2_VideoChromaSubsampling_MPEG2;
			break;
		case AVCHROMA_LOC_CENTER:
			fmt.VideoChromaSubsampling = DXVA2_VideoChromaSubsampling_MPEG1;
			break;
		case AVCHROMA_LOC_TOPLEFT:
			fmt.VideoChromaSubsampling = DXVA2_VideoChromaSubsampling_Cosited;
			break;
		case AVCHROMA_LOC_UNSPECIFIED:
			if (m_CodecId == AV_CODEC_ID_RAWVIDEO && m_inputDxvaExtFormat.VideoChromaSubsampling) {
				fmt.VideoChromaSubsampling = m_inputDxvaExtFormat.VideoChromaSubsampling;
			}
	}

	// Color Range, 0-255 or 16-235
	if (color_range == AVCOL_RANGE_JPEG) {
		fmt.NominalRange = DXVA2_NominalRange_0_255;
	}
	else if (color_range == AVCOL_RANGE_UNSPECIFIED && m_CodecId == AV_CODEC_ID_RAWVIDEO && m_inputDxvaExtFormat.NominalRange) {
		fmt.NominalRange = m_inputDxvaExtFormat.NominalRange;
	}
	else {
		fmt.NominalRange = DXVA2_NominalRange_16_235;
	}

	// HACK: 1280 is the value when only chroma location is set to MPEG2, do not bother to send this information, as its the same for basically every clip
	if ((fmt.value & ~0xff) != 0 && (fmt.value & ~0xff) != 1280) {
		fmt.SampleFormat = AMCONTROL_USED | AMCONTROL_COLORINFO_PRESENT;
	} else {
		fmt.value = 0;
	}

	return fmt;
}

static inline BOOL GOPFound(BYTE *buf, int len)
{
	if (buf && len > 0) {
		CGolombBuffer gb(buf, len);
		BYTE state = 0x00;
		while (gb.NextMpegStartCode(state)) {
			if (state == 0xb8) { // GOP
				return TRUE;
			}
		}
	}

	return FALSE;
}

HRESULT CMPCVideoDecFilter::FillAVPacket(const BYTE *buffer, int buflen)
{
	av_packet_unref(m_pPacket);

	int size = buflen;
	if (m_CodecId == AV_CODEC_ID_PRORES) {
		// code from ffmpeg/libavutil/mem.c -> av_fast_realloc()
		size = (buflen + AV_INPUT_BUFFER_PADDING_SIZE) + (buflen + AV_INPUT_BUFFER_PADDING_SIZE) / 16 + 32;
	}

	if (size > buflen) {
		if (av_new_packet(m_pPacket, size) < 0) {
			return E_OUTOFMEMORY;
		}
		memcpy(m_pPacket->data, buffer, buflen);
		memset(m_pPacket->data + buflen, 0, size - buflen);
	} else {
		m_pPacket->data = const_cast<uint8_t*>(buffer);
		m_pPacket->size = buflen;
	}

	return S_OK;
}

HRESULT CMPCVideoDecFilter::DecodeInternal(AVPacket *avpkt, REFERENCE_TIME rtStartIn, REFERENCE_TIME rtStopIn, BOOL bPreroll/* = FALSE*/)
{
	if (avpkt) {
		if (m_bWaitingForKeyFrame) {
			if (m_CodecId == AV_CODEC_ID_MPEG2VIDEO) {
				if (GOPFound(avpkt->data, avpkt->size)) {
					m_bWaitingForKeyFrame = FALSE;
				}
			}
			else if (m_CodecId == AV_CODEC_ID_VP8 || m_CodecId == AV_CODEC_ID_VP9) {
				const BOOL bKeyFrame = (m_CodecId == AV_CODEC_ID_VP8) ? !(avpkt->data[0] & 1) : !(avpkt->data[0] & 4);
				if (bKeyFrame) {
					DLog(L"CMPCVideoDecFilter::DecodeInternal(): Found VP8/9 key-frame, resuming decoding");
					m_bWaitingForKeyFrame = FALSE;
				} else {
					return S_OK;
				}
			}
		}

		if (m_bHasPalette) {
			m_bHasPalette = false;
			uint8_t* pal = av_packet_new_side_data(avpkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE);
			memcpy(pal, m_Palette, AVPALETTE_SIZE);
		}
	}

	int ret = avcodec_send_packet(m_pAVCtx, avpkt);
	if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
		if (ret == AVERROR_INVALIDDATA) {
			return S_FALSE;
		}

		if (UseDXVA2() && (!m_bDXVACompatible || m_bFailDXVA2Decode)) {
			CleanupDXVAVariables();
			CleanupD3DResources();
			m_pDXVADecoder.reset();
			m_nDecoderMode = MODE_SOFTWARE;
			m_hwType = HwType::None;
			DXVAState::ClearState();

			InitDecoder(&m_pCurrentMediaType);
			ChangeOutputMediaFormat(2);
		} else if (UseD3D11() && (!m_bDXVACompatible || m_bFailD3D11Decode)) {
			m_nDecoderMode = MODE_SOFTWARE;
			m_hwType = HwType::None;
			DXVAState::ClearState();

			InitDecoder(&m_pCurrentMediaType);
			ChangeOutputMediaFormat(2);
		} else if ((m_hwType == HwType::D3D11CopyBack || m_hwType == HwType::D3D12CopyBack || m_hwType == HwType::NVDEC)
				   && !m_bDecoderAcceptFormat) {
			m_FormatConverter.Clear();
			DXVAState::ClearState();
			m_HWPixFmt = AV_PIX_FMT_NONE;
			m_hwType = HwType::None;
			InitDecoder(&m_pCurrentMediaType);
			ChangeOutputMediaFormat(2);
		}

		return S_FALSE;
	}

	for (;;) {
		auto frame = (m_HWPixFmt == AV_PIX_FMT_NONE) ? m_pFrame : m_pHWFrame;

		ret = avcodec_receive_frame(m_pAVCtx, frame);
		if (ret < 0 && ret != AVERROR(EAGAIN)) {
			av_frame_unref(frame);
			return S_FALSE;
		}

		if (m_bWaitKeyFrame) {
			if (m_bWaitingForKeyFrame && ret >= 0) {
				if (frame->flags & AV_FRAME_FLAG_KEY) {
					DLog(L"CMPCVideoDecFilter::DecodeInternal(): Found key-frame, resuming decoding");
					m_bWaitingForKeyFrame = FALSE;
				} else {
					ret = AVERROR(EAGAIN);
				}
			}
		}

		if (ret < 0 || !frame->data[0]) {
			av_frame_unref(frame);
			break;
		}

		if (m_HWPixFmt != AV_PIX_FMT_NONE) {
			if (m_pHWFrame->format != m_HWPixFmt) {
				m_FormatConverter.Clear();
				DXVAState::ClearState();
				m_HWPixFmt = AV_PIX_FMT_NONE;
				m_hwType = HwType::None;
				InitDecoder(&m_pCurrentMediaType);
				ChangeOutputMediaFormat(2);

				return S_FALSE;
			}

			if (ret < 0) {
				av_frame_unref(frame);
				continue;
			}
			av_frame_copy_props(m_pFrame, m_pHWFrame);

			if (m_pHWFrame->format == m_HWPixFmt && !DXVAState::GetState()) {
				SetDXVAState();
				m_FormatConverter.SetDirect(TRUE);
			}
		}

		UpdateAspectRatio();

		HRESULT hr = S_OK;

		if (UseDXVA2()) {
			hr = m_pDXVADecoder->DeliverFrame();
			av_frame_unref(frame);
			continue;
		} else if (UseD3D11()) {
			hr = m_pD3D11Decoder->DeliverFrame();
			av_frame_unref(frame);
			continue;
		}

		GetFrameTimeStamp(m_pFrame, rtStartIn, rtStopIn);
		if (m_bRVDropBFrameTimings && m_pFrame->pict_type == AV_PICTURE_TYPE_B) {
			rtStartIn = m_rtLastStop;
			rtStopIn = INVALID_TIME;
		}

		bool bSampleTime = !(m_CodecId == AV_CODEC_ID_MJPEG && rtStartIn == INVALID_TIME);
		UpdateFrameTime(rtStartIn, rtStopIn);

		if (bPreroll || rtStartIn < 0) {
			av_frame_unref(frame);
			continue;
		}

		CComPtr<IMediaSample> pOut;
		BYTE* pDataOut = nullptr;
		DXVA2_ExtendedFormat dxvaExtFormat = GetDXVA2ExtendedFormat(m_pAVCtx, m_pFrame);

		int w = frame->width;
		int h = frame->height;

		if (FAILED(hr = GetDeliveryBuffer(w, h, &pOut, GetFrameDuration(), &dxvaExtFormat)) || FAILED(hr = pOut->GetPointer(&pDataOut))) {
			av_frame_unref(frame);
			continue;
		}

		if (m_HWPixFmt != AV_PIX_FMT_NONE && m_pHWFrame->hw_frames_ctx) {
			auto frames_ctx = (AVHWFramesContext*)m_pHWFrame->hw_frames_ctx->data;

			if (frames_ctx->format == AV_PIX_FMT_D3D11) {
				auto device_hwctx = reinterpret_cast<AVD3D11VADeviceContext*>(frames_ctx->device_ctx->hwctx);
				auto texture = reinterpret_cast<ID3D11Texture2D*>(m_pHWFrame->data[0]);
				auto index = reinterpret_cast<intptr_t>(m_pHWFrame->data[1]);

				D3D11_TEXTURE2D_DESC inputTexDesc = {};
				texture->GetDesc(&inputTexDesc);

				D3D11_TEXTURE2D_DESC texDesc = {};
				if (m_pStagingD3D11Texture2D) {
					m_pStagingD3D11Texture2D->GetDesc(&texDesc);
					if (texDesc.Format != inputTexDesc.Format || texDesc.Width != inputTexDesc.Width || texDesc.Height != inputTexDesc.Height) {
						m_pStagingD3D11Texture2D.Release();
					}
				}
				if (!m_pStagingD3D11Texture2D) {
					texDesc.Width          = inputTexDesc.Width;
					texDesc.Height         = inputTexDesc.Height;
					texDesc.MipLevels      = 1;
					texDesc.Format         = inputTexDesc.Format;
					texDesc.SampleDesc     = { 1, 0 };
					texDesc.ArraySize      = 1;
					texDesc.Usage          = D3D11_USAGE_STAGING;
					texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
					hr = device_hwctx->device->CreateTexture2D(&texDesc, nullptr, &m_pStagingD3D11Texture2D);
					if (FAILED(hr)) {
						DLog(L"CMPCVideoDecFilter::DecodeInternal() : ID3D11Device::CreateTexture2D() failed : %s", HR2Str(hr));\
						av_frame_unref(frame);
						continue;
					}
				}

				device_hwctx->device_context->CopySubresourceRegion(m_pStagingD3D11Texture2D, 0, 0, 0, 0, texture, index, nullptr);

				device_hwctx->lock(device_hwctx->lock_ctx);

				D3D11_MAPPED_SUBRESOURCE mappedResource = {};
				hr = device_hwctx->device_context->Map(m_pStagingD3D11Texture2D, 0, D3D11_MAP_READ, 0, &mappedResource);
				if (FAILED(hr)) {
					DLog(L"CMPCVideoDecFilter::DecodeInternal() : ID3D11DeviceContext::Map() failed : %s", HR2Str(hr));
					av_frame_unref(frame);
					continue;
				}

				int codedbytes = 1;
				switch (frames_ctx->sw_format) {
					case AV_PIX_FMT_P010:
					case AV_PIX_FMT_P016:
					case AV_PIX_FMT_YUYV422:
						codedbytes = 2;
						break;
					case AV_PIX_FMT_Y210:
					case AV_PIX_FMT_Y212:
					case AV_PIX_FMT_VUYX:
					case AV_PIX_FMT_XV30:
						codedbytes = 4;
						break;
					case AV_PIX_FMT_XV36:
						codedbytes = 8;
						break;
				}

				auto width = mappedResource.RowPitch / codedbytes;

				av_image_fill_linesizes(m_pFrame->linesize, frames_ctx->sw_format, width);
				av_image_fill_pointers(m_pFrame->data, frames_ctx->sw_format, texDesc.Height, reinterpret_cast<uint8_t*>(mappedResource.pData), m_pFrame->linesize);

				m_pFrame->format = frames_ctx->sw_format;
				m_pFrame->width  = m_pHWFrame->width;
				m_pFrame->height = m_pHWFrame->height;

				m_FormatConverter.Converting(pDataOut, m_pFrame);

				device_hwctx->device_context->Unmap(m_pStagingD3D11Texture2D, 0);

				device_hwctx->unlock(device_hwctx->lock_ctx);
			}
			else if (frames_ctx->format == AV_PIX_FMT_D3D12) {
				ret = d3d12va_direct_copy(m_pHWFrame, m_pFrame, pDataOut,
										  +[](void* ptr, AVFrame* frame, uint8_t* data) {
					auto pFilter = static_cast<CMPCVideoDecFilter*>(ptr);
					pFilter->m_FormatConverter.Converting(data, frame);
				}, this);
				if (ret < 0) {
					av_frame_unref(frame);
					continue;
				}
			}
			else if (frames_ctx->format == AV_PIX_FMT_CUDA && m_FormatConverter.DirectCopyPossible(frames_ctx->sw_format)) {
				auto device_hwctx = reinterpret_cast<AVHWDeviceContext*>(frames_ctx->device_ctx);
				auto cuda_hwctx = reinterpret_cast<AVCUDADeviceContext*>(device_hwctx->hwctx);
				auto cuda_fns = cuda_hwctx->internal->cuda_dl;

				auto cuStatus = cuda_fns->cuCtxPushCurrent(cuda_hwctx->cuda_ctx);
				if (cuStatus != CUDA_SUCCESS) {
					DLog(L"CMPCVideoDecFilter::DecodeInternal() : Cuda cuCtxPushCurrent() failed");
					av_frame_unref(frame);
					continue;
				}
				int linesize[4] = {};
				av_image_fill_linesizes(linesize, frames_ctx->sw_format, m_FormatConverter.GetDstStride());

				int h_shift = 0, v_shift = 0;
				av_pix_fmt_get_chroma_sub_sample(frames_ctx->sw_format, &h_shift, &v_shift);

				uint8_t* hwdata[4];
				memcpy(hwdata, m_pHWFrame->data, sizeof(hwdata)); // copy 4 pointers from 8 (AV_NUM_DATA_POINTERS)
				if (m_FormatConverter.GetOutPixFormat() == PixFmt_YV24) {
					std::swap(hwdata[1], hwdata[2]); // swap UV when YUV444P to YV24
				}

				unsigned offset = 0;
				for (size_t i = 0; i < std::size(hwdata) && hwdata[i]; i++) {
					CUDA_MEMCPY2D cpy = {};

					cpy.srcPitch      = m_pHWFrame->linesize[i];
					cpy.dstPitch      = linesize[i];
					cpy.WidthInBytes  = std::min(cpy.srcPitch, cpy.dstPitch);
					cpy.Height        = m_pHWFrame->height >> ((i == 0 || i == 3) ? 0 : v_shift);

					cpy.srcMemoryType = CU_MEMORYTYPE_DEVICE;
					cpy.srcDevice     = reinterpret_cast<CUdeviceptr>(hwdata[i]);

					cpy.dstMemoryType = CU_MEMORYTYPE_HOST;
					cpy.dstHost       = pDataOut + offset;

					cuStatus = cuda_fns->cuMemcpy2DAsync(&cpy, cuda_hwctx->stream);
					if (cuStatus != CUDA_SUCCESS) {
						break;
					}

					offset += linesize[i] * (m_pHWFrame->height >> (i ? v_shift : 0));
				}

				if (cuStatus != CUDA_SUCCESS) {
					DLog(L"CMPCVideoDecFilter::DecodeInternal() : Cuda cuMemcpy2DAsync() failed");
					av_frame_unref(frame);
					continue;
				}

				cuda_fns->cuStreamSynchronize(cuda_hwctx->stream);
				cuda_fns->cuCtxPopCurrent(nullptr);
			} else {
				ret = av_hwframe_transfer_data(m_pFrame, m_pHWFrame, 0);
				if (ret < 0) {
					av_frame_unref(frame);
					continue;
				}
				m_FormatConverter.Converting(pDataOut, m_pFrame);
			}
		}
		else {
			// Check alignment on rawvideo, which can be off depending on the source file
			AVFrame* pTmpFrame = nullptr;
			if (m_CodecId == AV_CODEC_ID_RAWVIDEO) {
				for (size_t i = 0; i < 4; i++) {
					if ((intptr_t)m_pFrame->data[i] % 16u || m_pFrame->linesize[i] % 16u) {
						// copy the frame, its not aligned properly and would crash later
						pTmpFrame = av_frame_alloc();
						pTmpFrame->format      = m_pFrame->format;
						pTmpFrame->width       = m_pFrame->width;
						pTmpFrame->height      = m_pFrame->height;
						pTmpFrame->colorspace  = m_pFrame->colorspace;
						pTmpFrame->color_range = m_pFrame->color_range;
						av_frame_get_buffer(pTmpFrame, AV_INPUT_BUFFER_PADDING_SIZE);
						av_image_copy(pTmpFrame->data, pTmpFrame->linesize, (const uint8_t**)m_pFrame->data, m_pFrame->linesize, (AVPixelFormat)m_pFrame->format, m_pFrame->width, m_pFrame->height);
						break;
					}
				}
			}

			if (pTmpFrame) {
				m_FormatConverter.Converting(pDataOut, pTmpFrame);
				av_frame_free(&pTmpFrame);
			} else {
				m_FormatConverter.Converting(pDataOut, m_pFrame);
			}
		}

		if (bSampleTime) {
			pOut->SetTime(&rtStartIn, &rtStopIn);
		}
		pOut->SetMediaTime(nullptr, nullptr);
		SetTypeSpecificFlags(pOut);

		AddFrameSideData(pOut, m_pFrame);

		hr = m_pOutput->Deliver(pOut);

		av_frame_unref(frame);

		m_bDecoderAcceptFormat = TRUE;
	}

	return S_OK;
}

HRESULT CMPCVideoDecFilter::ParseInternal(const BYTE *buffer, int buflen, REFERENCE_TIME rtStartIn, REFERENCE_TIME rtStopIn, BOOL bPreroll)
{
	BOOL bFlush = (buffer == nullptr);
	BYTE* pDataBuffer = (BYTE*)buffer;
	HRESULT hr = S_OK;

	if (buflen > 0 && buffer) {
		// re-allocate buffer to have enough space
		auto pBuf = reinterpret_cast<BYTE*>(av_fast_realloc(m_pFFBuffer, &m_nFFBufferSize, static_cast<size_t>(buflen) + AV_INPUT_BUFFER_PADDING_SIZE));
		if (!pBuf) {
			return E_FAIL;
		}

		m_pFFBuffer = pBuf;

		// copy data to buffer
		memcpy(m_pFFBuffer, buffer, buflen);
		memset(m_pFFBuffer + buflen, 0, AV_INPUT_BUFFER_PADDING_SIZE);
		pDataBuffer = m_pFFBuffer;
	}

	while (buflen > 0 || bFlush) {
		REFERENCE_TIME rtStart = rtStartIn, rtStop = rtStopIn;

		BYTE *pOutBuffer = nullptr;
		int pOutLen = 0;

		int used_bytes = av_parser_parse2(m_pParser, m_pAVCtx, &pOutBuffer, &pOutLen, pDataBuffer, buflen, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
		if (used_bytes == 0 && pOutLen == 0 && !bFlush) {
			DLog(L"CMPCVideoDecFilter::ParseInternal() - could not process buffer, starving?");
			break;
		} else if (used_bytes > 0) {
			buflen -= used_bytes;
			pDataBuffer += used_bytes;
		}

		// Update start time cache
		// If more data was read then output, update the cache (incomplete frame)
		// If output is bigger or equal, a frame was completed, update the actual rtStart with the cached value, and then overwrite the cache
		if (used_bytes > pOutLen) {
			if (rtStartIn != INVALID_TIME) {
				m_rtStartCache = rtStartIn;
			}
		/*
		} else if (used_bytes == pOutLen || ((used_bytes + 9) == pOutLen)) {
			// Why +9 above?
			// Well, apparently there are some broken MKV muxers that like to mux the MPEG-2 PICTURE_START_CODE block (which is 9 bytes) in the package with the previous frame
			// This would cause the frame timestamps to be delayed by one frame exactly, and cause timestamp reordering to go wrong.
			// So instead of failing on those samples, lets just assume that 9 bytes are that case exactly.
			m_rtStartCache = rtStartIn = INVALID_TIME;
		} else if (pOut_size > used_bytes) {
		*/
		} else {
			rtStart        = m_rtStartCache;
			m_rtStartCache = rtStartIn;
			// The value was used once, don't use it for multiple frames, that ends up in weird timings
			rtStartIn      = INVALID_TIME;
		}

		if (pOutLen > 0) {
			if (FAILED(hr = FillAVPacket(pOutBuffer, pOutLen))) {
				break;
			}

			m_pPacket->pts = rtStart;

			hr = DecodeInternal(m_pPacket, rtStartIn, rtStopIn, bPreroll);

			if (FAILED(hr)) {
				break;
			}
		} else if (bFlush) {
			hr = DecodeInternal(nullptr, INVALID_TIME, INVALID_TIME);
			break;
		}
	}

	return hr;
}

HRESULT CMPCVideoDecFilter::Decode(const BYTE *buffer, int buflen, REFERENCE_TIME rtStartIn, REFERENCE_TIME rtStopIn, BOOL bSyncPoint/* = FALSE*/, BOOL bPreroll/* = FALSE*/)
{
	HRESULT hr = S_OK;

	if (m_bReorderBFrame) {
		m_tBFrameDelay[m_nBFramePos].rtStart = rtStartIn;
		m_tBFrameDelay[m_nBFramePos].rtStop  = rtStopIn;
		m_nBFramePos = !m_nBFramePos;
	}

	if (m_pParser) {
		hr = ParseInternal(buffer, buflen, rtStartIn, rtStopIn, bPreroll);
	} else {
		if (!buffer) {
			return DecodeInternal(nullptr, INVALID_TIME, INVALID_TIME);
		}

		if (FAILED(FillAVPacket(buffer, buflen))) {
			return E_OUTOFMEMORY;
		}

		m_pPacket->pts  = rtStartIn;
		if (rtStartIn != INVALID_TIME && rtStopIn != INVALID_TIME) {
			m_pPacket->duration = rtStopIn - rtStartIn;
		}
		m_pPacket->flags = bSyncPoint ? AV_PKT_FLAG_KEY : 0;

		hr = DecodeInternal(m_pPacket, rtStartIn, rtStopIn, bPreroll);
	}

	return hr;
}

// change colorspace details/output media format
// 1 - change swscaler colorspace details
// 2 - change output media format
HRESULT CMPCVideoDecFilter::ChangeOutputMediaFormat(int nType)
{
	HRESULT hr = S_OK;

	if (!m_pOutput || !m_pOutput->IsConnected()) {
		return hr;
	}

	// change swscaler colorspace details
	if (nType >= 1) {
		m_FormatConverter.SetOptions(m_nSwRGBLevels);
	}

	// change output media format
	if (nType == 2) {
		CAutoLock cObjectLock(m_pLock);
		BuildOutputFormat();

		IPin* pPin = m_pOutput->GetConnected();
		if (IsVideoRenderer(GetFilterFromPin(pPin))) {
			hr = NotifyEvent(EC_DISPLAY_CHANGED, (LONG_PTR)pPin, 0);
			if (S_OK != hr) {
				hr = E_FAIL;
			}
		} else {
			int nNumber;
			VIDEO_OUTPUT_FORMATS* pFormats;
			GetOutputFormats(nNumber, &pFormats);
			for (int i = 0; i < nNumber * 2; i++) {
				CMediaType mt;
				if (SUCCEEDED(GetMediaType(i, &mt))) {
					hr = pPin->QueryAccept(&mt);
					if (hr == S_OK) {
						hr = ReconnectPin(pPin, &mt);
						if (hr == S_OK) {
							return hr;
						}
					}
				}
			}

			return E_FAIL;
		}
	}

	return hr;
}

void CMPCVideoDecFilter::SetDXVAState()
{
	CString codec;
	switch (m_CodecId) {
	case AV_CODEC_ID_AV1:        codec = L"AV1";    break;
	case AV_CODEC_ID_H264:       codec = L"H.264";  break;
	case AV_CODEC_ID_HEVC:       codec = L"HEVC";   break;
	case AV_CODEC_ID_MPEG2VIDEO: codec = L"MPEG-2"; break;
	case AV_CODEC_ID_VC1:
	case AV_CODEC_ID_WMV3:       codec = L"VC-1";   break;
	case AV_CODEC_ID_VP9:        codec = L"VP9";    break;
	}

	auto frames_ctx = reinterpret_cast<AVHWFramesContext*>(m_pHWFrame->hw_frames_ctx->data);

	CString description = m_hwType == HwType::D3D11CopyBack ? L"D3D11 Copy-back" :
		(m_hwType == HwType::D3D12CopyBack ? L"D3D12 Copy-back" : L"NVDEC");
	if (!codec.IsEmpty()) {
		if (m_hwType == HwType::NVDEC) {
			if (frames_ctx->sw_format == AV_PIX_FMT_YUV444P || frames_ctx->sw_format == AV_PIX_FMT_YUV444P16) {
				codec.Append(L" 444");
			}
		}
		else if (m_hwType == HwType::D3D11CopyBack) {
			switch (frames_ctx->sw_format) {
			case AV_PIX_FMT_YUYV422:
			case AV_PIX_FMT_Y210:
			case AV_PIX_FMT_Y212:
				codec.Append(L" 422");
				break;
			case AV_PIX_FMT_VUYX:
			case AV_PIX_FMT_XV30:
			case AV_PIX_FMT_XV36:
				codec.Append(L" 444");
				break;
			}
		}

		const int depth = GetLumaBits(m_pAVCtx->sw_pix_fmt);
		if (depth > 8) {
			codec.AppendFormat(L" %d-bit", depth);
		}

		description += L", " + codec;
	}

	DXVAState::SetActiveState(GUID_NULL, description);

	if (frames_ctx->format == AV_PIX_FMT_CUDA) {
		auto device_hwctx = reinterpret_cast<AVHWDeviceContext*>(frames_ctx->device_ctx);
		auto cuda_hwctx = reinterpret_cast<AVCUDADeviceContext*>(device_hwctx->hwctx);
		auto cuda_fns = cuda_hwctx->internal->cuda_dl;

		char name[256] = {};
		auto cuStatus = cuda_fns->cuDeviceGetName(name, 256, 0);
		if (cuStatus == CUDA_SUCCESS) {
			const auto deviceName = UTF8ToWStr(name);
			if (!StartsWith(m_strDeviceDescription, deviceName.GetString())) {
				m_strDeviceDescription = deviceName;
			}
		}
	}

	if (frames_ctx->format == AV_PIX_FMT_D3D11) {
		auto device_hwctx = reinterpret_cast<AVD3D11VADeviceContext*>(frames_ctx->device_ctx->hwctx);
		const auto deviceName = UTF8ToWStr(device_hwctx->device_name);
		if (!deviceName.IsEmpty()) {
			m_strDeviceDescription = deviceName;
		}
	}

	if (frames_ctx->format == AV_PIX_FMT_D3D12) {
		auto device_hwctx = reinterpret_cast<AVD3D12VADeviceContext*>(frames_ctx->device_ctx->hwctx);
		const auto deviceName = UTF8ToWStr(device_hwctx->device_name);
		if (!deviceName.IsEmpty()) {
			m_strDeviceDescription = deviceName;
		}
	}
}

void CMPCVideoDecFilter::SetThreadCount()
{
	if (m_pAVCtx) {
		if (m_hwType == HwType::NVDEC || m_hwType == HwType::D3D12CopyBack
				|| m_CodecId == AV_CODEC_ID_MPEG4 || IsDXVASupported(m_hwType == HwType::DXVA2 || m_hwType == HwType::D3D11)) {
			m_pAVCtx->thread_count = 1;
			m_pAVCtx->thread_type  = 0;
		} else {
			int nThreadNumber = (m_nThreadNumber > 0) ? m_nThreadNumber : CPUInfo::GetProcessorNumber();
			m_pAVCtx->thread_count = std::clamp(nThreadNumber, 1, MAX_AUTO_THREADS);
		}
	}
}

HRESULT CMPCVideoDecFilter::Transform(IMediaSample* pIn)
{
	if (m_bReinit) {
		DLog(L"CMPCVideoDecFilter::Transform() - skip sample during initialization");
		return S_OK;
	}

	HRESULT			hr;
	BYTE*			buffer;
	int				buflen;
	REFERENCE_TIME	rtStart	= INVALID_TIME;
	REFERENCE_TIME	rtStop	= INVALID_TIME;

	if (FAILED(hr = pIn->GetPointer(&buffer))) {
		return hr;
	}

	buflen = pIn->GetActualDataLength();
	if (buflen == 0) {
		return S_OK;
	}

	hr = pIn->GetTime(&rtStart, &rtStop);
	if (FAILED(hr)) {
		rtStart = rtStop = INVALID_TIME;
		DLogIf(!m_bDecodingStart, L"CMPCVideoDecFilter::Transform(): input sample without timestamps!");
	} else if (hr == VFW_S_NO_STOP_TIME || rtStop - 1 <= rtStart) {
		rtStop = INVALID_TIME;
	}

	if (UseDXVA2()) {
		CheckPointer(m_pDXVA2Allocator, E_UNEXPECTED);
	}

	hr = m_pMSDKDecoder
		? m_pMSDKDecoder->Decode(buffer, buflen, rtStart, rtStop)
		: Decode(buffer, buflen, rtStart, rtStop, pIn->IsSyncPoint() == S_OK, pIn->IsPreroll() == S_OK);

	m_bDecodingStart = TRUE;

	return hr;
}

void CMPCVideoDecFilter::UpdateAspectRatio()
{
	if (m_nARMode) {
		bool bSetAR = true;
		if (m_nARMode == 2) {
			CMediaType& mt = m_pInput->CurrentMediaType();
			if (mt.formattype == FORMAT_VideoInfo2 || mt.formattype == FORMAT_MPEG2_VIDEO || mt.formattype == FORMAT_DiracVideoInfo) {
				VIDEOINFOHEADER2* vih2 = (VIDEOINFOHEADER2*)mt.pbFormat;
				bSetAR = (!vih2->dwPictAspectRatioX && !vih2->dwPictAspectRatioY);
			}
			if (!bSetAR && (m_nARX && m_nARY)) {
				CSize aspect(m_nARX, m_nARY);
				ReduceDim(aspect);
				SetAspect(aspect);
			}
		}

		if (bSetAR) {
			if (m_pAVCtx && (m_pAVCtx->sample_aspect_ratio.num > 0) && (m_pAVCtx->sample_aspect_ratio.den > 0)) {
				CSize aspect(m_pAVCtx->sample_aspect_ratio.num * m_pAVCtx->width, m_pAVCtx->sample_aspect_ratio.den * m_pAVCtx->height);
				ReduceDim(aspect);
				SetAspect(aspect);
			}
		}
	} else if (m_nARX && m_nARY) {
		CSize aspect(m_nARX, m_nARY);
		ReduceDim(aspect);
		SetAspect(aspect);
	}
}

void CMPCVideoDecFilter::FlushDXVADecoder()	{
	if (m_pDXVADecoder) {
		CAutoLock cAutoLock(&m_csReceive);
		if (m_pAVCtx && avcodec_is_open(m_pAVCtx)) {
			avcodec_flush_buffers(m_pAVCtx);
		}
	}
}

void CMPCVideoDecFilter::FillInVideoDescription(DXVA2_VideoDesc& videoDesc, D3DFORMAT Format/* = D3DFMT_A8R8G8B8*/)
{
	memset(&videoDesc, 0, sizeof(videoDesc));
	videoDesc.SampleWidth        = m_nSurfaceWidth;
	videoDesc.SampleHeight       = m_nSurfaceHeight;
	videoDesc.Format             = Format;
	videoDesc.UABProtectionLevel = 1;
}

BOOL CMPCVideoDecFilter::IsSupportedDecoderMode(const GUID& decoderGUID)
{
	if (IsDXVASupported(m_hwType == HwType::DXVA2 || m_hwType == HwType::D3D11)) {
		for (const auto& mode : DXVAModes) {
			if (mode.nCodecId == m_CodecId
					&& mode.decoderGUID == decoderGUID) {
				if (m_pAVCtx->codec_id == AV_CODEC_ID_HEVC && m_pAVCtx->profile == AV_PROFILE_HEVC_REXT) {
					if (mode.pixFormat != AV_PIX_FMT_NONE) {
						const auto pix_fmt = (m_pAVCtx->sw_pix_fmt != AV_PIX_FMT_NONE) ? m_pAVCtx->sw_pix_fmt : m_pAVCtx->pix_fmt;
						if (mode.pixFormat == pix_fmt) {
							return TRUE;
						}
					}
				} else if (mode.bHighBitdepth == m_bHighBitdepth) {
					return TRUE;
				}
			}
		}
	}

	return FALSE;
}

BOOL CMPCVideoDecFilter::IsSupportedDecoderConfig(const D3DFORMAT& nD3DFormat, const DXVA2_ConfigPictureDecode& config, bool& bIsPrefered)
{
	bIsPrefered = (config.ConfigBitstreamRaw == (m_CodecId == AV_CODEC_ID_H264 ? 2 : 1));
	return (m_bHighBitdepth && nD3DFormat == FCC('P010') || (!m_bHighBitdepth && nD3DFormat == FCC('NV12')));
}

HRESULT CMPCVideoDecFilter::FindDXVA2DecoderConfiguration(IDirectXVideoDecoderService *pDecoderService,
														  const GUID& guidDecoder,
														  DXVA2_ConfigPictureDecode& selectedConfig,
														  D3DFORMAT& surfaceFormat,
														  BOOL& bFoundDXVA2Configuration)
{
	HRESULT hr = S_OK;
	UINT cFormats = 0;
	UINT cConfigurations = 0;
	bool bIsPrefered = false;

	D3DFORMAT                 *pFormats = nullptr;
	DXVA2_ConfigPictureDecode *pConfig  = nullptr;

	// Find the valid render target formats for this decoder GUID.
	hr = pDecoderService->GetDecoderRenderTargets(guidDecoder, &cFormats, &pFormats);

	if (SUCCEEDED(hr)) {
		// Look for a format that matches our output format.
		for (UINT iFormat = 0; iFormat < cFormats;  iFormat++) {

			// Fill in the video description. Set the width, height, format, and frame rate.
			DXVA2_VideoDesc VideoDesc;
			FillInVideoDescription(VideoDesc, pFormats[iFormat]);

			// Get the available configurations.
			hr = pDecoderService->GetDecoderConfigurations(guidDecoder, &VideoDesc, nullptr, &cConfigurations, &pConfig);
			if (FAILED(hr)) {
				continue;
			}

			// Find a supported configuration.
			for (UINT iConfig = 0; iConfig < cConfigurations; iConfig++) {
				if (IsSupportedDecoderConfig(pFormats[iFormat], pConfig[iConfig], bIsPrefered)) {
					// This configuration is good.
					if (bIsPrefered || !bFoundDXVA2Configuration) {
						bFoundDXVA2Configuration = TRUE;
						selectedConfig           = pConfig[iConfig];
						surfaceFormat            = pFormats[iFormat];

						FillInVideoDescription(m_VideoDesc, pFormats[iFormat]);
					}

					if (bIsPrefered) {
						break;
					}
				}
			}

			CoTaskMemFree(pConfig);
		} // End of formats loop.
	}

	CoTaskMemFree(pFormats);

	// Note: It is possible to return S_OK without finding a configuration.
	return hr;
}

HRESULT CMPCVideoDecFilter::ConfigureDXVA2(IPin *pPin)
{
	HRESULT hr = S_OK;

	CleanupD3DResources();

	CComPtr<IMFGetService> pGetService;

	// Query the pin for IMFGetService.
	hr = pPin->QueryInterface(IID_PPV_ARGS(&pGetService));

	// Get the Direct3D device manager.
	if (SUCCEEDED(hr)) {
		hr = pGetService->GetService(MR_VIDEO_ACCELERATION_SERVICE, IID_PPV_ARGS(&m_pDeviceManager));
	}

	// Open a new device handle.
	if (SUCCEEDED(hr)) {
		hr = m_pDeviceManager->OpenDeviceHandle(&m_hDevice);
	}

	// Get the video decoder service.
	if (SUCCEEDED(hr)) {
		hr = m_pDeviceManager->GetVideoService(m_hDevice, IID_PPV_ARGS(&m_pDecoderService));
	}

	if (SUCCEEDED(hr)) {
		hr = FindDecoderConfiguration();
	}

	if (SUCCEEDED(hr)) {
		const auto& mt = m_pOutput->CurrentMediaType();
		if ((m_DXVASurfaceFormat == FCC('NV12') && mt.subtype != MEDIASUBTYPE_NV12)
				|| (m_DXVASurfaceFormat == FCC('P010') && mt.subtype != MEDIASUBTYPE_P010)) {
			hr = E_FAIL;
		}
	}

	if (FAILED(hr)) {
		CleanupD3DResources();
	}

	return hr;
}

HRESULT CMPCVideoDecFilter::SetEVRForDXVA2(IPin *pPin)
{
	CComPtr<IMFGetService> pGetService;
	HRESULT hr = pPin->QueryInterface(IID_PPV_ARGS(&pGetService));
	if (SUCCEEDED(hr)) {
		CComPtr<IDirectXVideoMemoryConfiguration> pVideoConfig;
		hr = pGetService->GetService(MR_VIDEO_ACCELERATION_SERVICE, IID_PPV_ARGS(&pVideoConfig));
		if (SUCCEEDED(hr)) {
			// Notify the EVR.
			DXVA2_SurfaceType surfaceType;
			DWORD dwTypeIndex = 0;
			for (;;) {
				hr = pVideoConfig->GetAvailableSurfaceTypeByIndex(dwTypeIndex, &surfaceType);
				if (FAILED(hr)) {
					break;
				}
				if (surfaceType == DXVA2_SurfaceType_DecoderRenderTarget) {
					hr = pVideoConfig->SetSurfaceType(DXVA2_SurfaceType_DecoderRenderTarget);
					break;
				}
				++dwTypeIndex;
			}
		}
	}

	return hr;
}

HRESULT CMPCVideoDecFilter::CreateDXVA2Decoder(LPDIRECT3DSURFACE9* ppDecoderRenderTargets, UINT nNumRenderTargets)
{
	DLog(L"CMPCVideoDecFilter::CreateDXVA2Decoder()");

	m_pDXVADecoder.reset();
	CComPtr<IDirectXVideoDecoder> pDirectXVideoDec;

	HRESULT hr = m_pDecoderService->CreateVideoDecoder(
		m_DXVADecoderGUID,
		&m_VideoDesc,
		&m_DXVA2Config,
		ppDecoderRenderTargets,
		nNumRenderTargets,
		&pDirectXVideoDec
	);

	if (SUCCEEDED(hr)) {
		m_pDXVADecoder = std::make_unique<CDXVA2Decoder>(this, pDirectXVideoDec, &m_DXVADecoderGUID, &m_DXVA2Config, ppDecoderRenderTargets, nNumRenderTargets);
		DLog(L"DXVA2 decoder successfully created %s", HR2Str(hr));
	}
	else {
		CleanupDXVAVariables();
		DLog(L"DXVA2 decoder creation failed with error %s", HR2Str(hr));
	}

	return hr;
}

HRESULT CMPCVideoDecFilter::ReinitDXVA2Decoder()
{
	HRESULT hr = E_FAIL;

	m_pDXVADecoder.reset();
	if (m_pDXVA2Allocator && IsDXVASupported(m_hwType == HwType::DXVA2) && SUCCEEDED(FindDecoderConfiguration())) {
		hr = RecommitAllocator();
	}

	return hr;
}

HRESULT CMPCVideoDecFilter::InitAllocator(IMemAllocator **ppAlloc)
{
	if (UseDXVA2()) {
		HRESULT hr = S_FALSE;
		m_pDXVA2Allocator = new(std::nothrow) CVideoDecDXVAAllocator(this, &hr);
		if (!m_pDXVA2Allocator) {
			return E_OUTOFMEMORY;
		}
		if (FAILED(hr)) {
			SAFE_DELETE(m_pDXVA2Allocator);
			return hr;
		}

		// Return the IMemAllocator interface.
		return m_pDXVA2Allocator->QueryInterface(IID_PPV_ARGS(ppAlloc));
	} else {
		return m_pD3D11Decoder->InitAllocator(ppAlloc);
	}
}

HRESULT CMPCVideoDecFilter::RecommitAllocator()
{
	HRESULT hr = S_OK;

	if (m_pDXVA2Allocator) {
		// Re-Commit the allocator (creates surfaces and new decoder)
		hr = m_pDXVA2Allocator->Decommit();
		GetOutputPin()->GetConnected()->BeginFlush();
		GetOutputPin()->GetConnected()->EndFlush();
		if (m_pDXVA2Allocator->DecommitInProgress()) {
			DLog(L"CMPCVideoDecFilter::RecommitAllocator() : WARNING! Flush had no effect, decommit of the allocator still not complete");
		}

		hr = m_pDXVA2Allocator->Commit();
	}

	return hr;
}

// ISpecifyPropertyPages2

STDMETHODIMP CMPCVideoDecFilter::GetPages(CAUUID* pPages)
{
	CheckPointer(pPages, E_POINTER);

#ifdef REGISTER_FILTER
	pPages->cElems    = 2;
#else
	pPages->cElems    = 1;
#endif
	pPages->pElems    = (GUID*)CoTaskMemAlloc(sizeof(GUID) * pPages->cElems);
	pPages->pElems[0] = __uuidof(CMPCVideoDecSettingsWnd);
#ifdef REGISTER_FILTER
	pPages->pElems[1] = __uuidof(CMPCVideoDecCodecWnd);
#endif

	return S_OK;
}

STDMETHODIMP CMPCVideoDecFilter::CreatePage(const GUID& guid, IPropertyPage** ppPage)
{
	CheckPointer(ppPage, E_POINTER);

	if (*ppPage != nullptr) {
		return E_INVALIDARG;
	}

	HRESULT hr;

	if (guid == __uuidof(CMPCVideoDecSettingsWnd)) {
		(*ppPage = DNew CInternalPropertyPageTempl<CMPCVideoDecSettingsWnd>(nullptr, &hr))->AddRef();
	}
#ifdef REGISTER_FILTER
	else if (guid == __uuidof(CMPCVideoDecCodecWnd)) {
		(*ppPage = DNew CInternalPropertyPageTempl<CMPCVideoDecCodecWnd>(nullptr, &hr))->AddRef();
	}
#endif

	return *ppPage ? S_OK : E_FAIL;
}

// EVR functions
HRESULT CMPCVideoDecFilter::DetectVideoCard_EVR(IPin *pPin)
{
	IMFGetService* pGetService;
	HRESULT hr = pPin->QueryInterface(IID_PPV_ARGS(&pGetService));
	if (SUCCEEDED(hr)) {
		// Try to get the adapter description of the active DirectX 9 device.
		IDirect3DDeviceManager9* pDevMan9;
		hr = pGetService->GetService(MR_VIDEO_ACCELERATION_SERVICE, IID_PPV_ARGS(&pDevMan9));
		if (SUCCEEDED(hr)) {
			HANDLE hDevice;
			hr = pDevMan9->OpenDeviceHandle(&hDevice);
			if (SUCCEEDED(hr)) {
				IDirect3DDevice9* pD3DDev9;
				hr = pDevMan9->LockDevice(hDevice, &pD3DDev9, TRUE);
				if (hr == DXVA2_E_NEW_VIDEO_DEVICE) {
					// Invalid device handle. Try to open a new device handle.
					hr = pDevMan9->CloseDeviceHandle(hDevice);
					if (SUCCEEDED(hr)) {
						hr = pDevMan9->OpenDeviceHandle(&hDevice);
						// Try to lock the device again.
						if (SUCCEEDED(hr)) {
							hr = pDevMan9->LockDevice(hDevice, &pD3DDev9, TRUE);
						}
					}
				}
				if (SUCCEEDED(hr)) {
					D3DDEVICE_CREATION_PARAMETERS DevPar9;
					hr = pD3DDev9->GetCreationParameters(&DevPar9);
					if (SUCCEEDED(hr)) {
						IDirect3D9* pD3D9;
						hr = pD3DDev9->GetDirect3D(&pD3D9);
						if (SUCCEEDED(hr)) {
							D3DADAPTER_IDENTIFIER9 AdapID9;
							hr = pD3D9->GetAdapterIdentifier(DevPar9.AdapterOrdinal, 0, &AdapID9);
							if (SUCCEEDED(hr)) {
								// copy adapter description
								m_nPCIVendor         = AdapID9.VendorId;
								m_nPCIDevice         = AdapID9.DeviceId;
								m_VideoDriverVersion = AdapID9.DriverVersion.QuadPart;
								if (SysVersion::IsWin81orLater() && (m_VideoDriverVersion & 0xffff00000000) == 0 && (m_VideoDriverVersion & 0xffff) == 0) {
									// fix bug in GetAdapterIdentifier()
									m_VideoDriverVersion = (m_VideoDriverVersion & 0xffff000000000000) | ((m_VideoDriverVersion & 0xffff0000) << 16) | 0xffffffff;
								}
								m_strDeviceDescription.Format(L"%S (%04X:%04X)", AdapID9.Description, m_nPCIVendor, m_nPCIDevice);
							}
						}
						pD3D9->Release();
					}
					pD3DDev9->Release();
					pDevMan9->UnlockDevice(hDevice, FALSE);
				}
				pDevMan9->CloseDeviceHandle(hDevice);
			}
			pDevMan9->Release();
		}
		pGetService->Release();
	}
	return hr;
}

HRESULT CMPCVideoDecFilter::SetFFMpegCodec(int nCodec, bool bEnabled)
{
	CAutoLock cAutoLock(&m_csProps);

	if (nCodec < 0 || nCodec >= VDEC_COUNT) {
		return E_FAIL;
	}

	m_VideoFilters[nCodec] = bEnabled;
	return S_OK;
}

// IFFmpegDecFilter
STDMETHODIMP CMPCVideoDecFilter::SaveSettings()
{
#ifdef REGISTER_FILTER
	CRegKey key;
	if (ERROR_SUCCESS == key.Create(HKEY_CURRENT_USER, OPT_REGKEY_VideoDec)) {
		key.SetDWORDValue(OPT_ThreadNumber, m_nThreadNumber);
		key.SetDWORDValue(OPT_DiscardMode, m_nDiscardMode);
		key.SetDWORDValue(OPT_ScanType, (int)m_nScanType);
		key.SetDWORDValue(OPT_ARMode, m_nARMode);
		for (int i = 0; i < HWCodec_count; i++) {
			key.SetDWORDValue(hwdec_opt_names[i], m_bHwCodecs[i]);
		}
		key.SetDWORDValue(OPT_HwDecoder, m_nHwDecoder);
		CStringW str;
		str.Format(L"%04X:%04X", m_HwAdapter.VendorId, m_HwAdapter.DeviceId);
		key.SetStringValue(OPT_HwAdapter, str);
		key.SetDWORDValue(OPT_DXVACheck, m_nDXVACheckCompatibility);
		key.SetDWORDValue(OPT_DisableDXVA_SD, m_nDXVA_SD);

		for (int i = 0; i < PixFmt_count; i++) {
			CString optname = OPT_SW_prefix;
			optname += GetSWOF(i)->name;
			key.SetDWORDValue(optname, m_fPixFmts[i]);
		}
		key.SetDWORDValue(OPT_SwConvertToRGB, m_bSwConvertToRGB);
		key.SetDWORDValue(OPT_SwRGBLevels, m_nSwRGBLevels);
	}
	if (ERROR_SUCCESS == key.Create(HKEY_CURRENT_USER, OPT_REGKEY_VCodecs)) {
		for (size_t i = 0; i < std::size(vcodecs); i++) {
			DWORD dw = m_nActiveCodecs & vcodecs[i].flag ? 1 : 0;
			key.SetDWORDValue(vcodecs[i].opt_name, dw);
		}
	}
#else
	CProfile& profile = AfxGetProfile();
	profile.WriteInt(OPT_SECTION_VideoDec, OPT_ThreadNumber, m_nThreadNumber);
	profile.WriteInt(OPT_SECTION_VideoDec, OPT_DiscardMode, m_nDiscardMode);
	profile.WriteInt(OPT_SECTION_VideoDec, OPT_ScanType, (int)m_nScanType);
	profile.WriteInt(OPT_SECTION_VideoDec, OPT_ARMode, m_nARMode);
	for (int i = 0; i < HWCodec_count; i++) {
		profile.WriteInt(OPT_SECTION_VideoDec, hwdec_opt_names[i], m_bHwCodecs[i]);
	}
	profile.WriteInt(OPT_SECTION_VideoDec, OPT_HwDecoder, m_nHwDecoder);
	CStringW str;
	str.Format(L"%04X:%04X", m_HwAdapter.VendorId, m_HwAdapter.DeviceId);
	profile.WriteString(OPT_SECTION_VideoDec, OPT_HwAdapter, str);
	profile.WriteInt(OPT_SECTION_VideoDec, OPT_DXVACheck, m_nDXVACheckCompatibility);
	profile.WriteInt(OPT_SECTION_VideoDec, OPT_DisableDXVA_SD, m_nDXVA_SD);
	profile.WriteBool(OPT_SECTION_VideoDec, OPT_SwConvertToRGB, m_bSwConvertToRGB);
	profile.WriteInt(OPT_SECTION_VideoDec, OPT_SwRGBLevels, m_nSwRGBLevels);
	for (int i = 0; i < PixFmt_count; i++) {
		CString optname = OPT_SW_prefix;
		optname += GetSWOF(i)->name;
		profile.WriteBool(OPT_SECTION_VideoDec, optname, m_fPixFmts[i]);
	}
#endif

	return S_OK;
}

// === IMPCVideoDecFilter

STDMETHODIMP CMPCVideoDecFilter::SetThreadNumber(int nValue)
{
	CAutoLock cAutoLock(&m_csProps);
	m_nThreadNumber = nValue;
	return S_OK;
}

STDMETHODIMP_(int) CMPCVideoDecFilter::GetThreadNumber()
{
	CAutoLock cAutoLock(&m_csProps);
	return m_nThreadNumber;
}

STDMETHODIMP CMPCVideoDecFilter::SetDiscardMode(int nValue)
{
	if (nValue != AVDISCARD_DEFAULT && nValue != AVDISCARD_NONREF) {
		return E_INVALIDARG;
	}

	CAutoLock cAutoLock(&m_csProps);
	m_nDiscardMode = nValue;

	if (m_pAVCtx) {
		m_pAVCtx->skip_frame = (AVDiscard)m_nDiscardMode;
	}

	return S_OK;
}

STDMETHODIMP_(int) CMPCVideoDecFilter::GetDiscardMode()
{
	CAutoLock cAutoLock(&m_csProps);
	return m_nDiscardMode;
}

STDMETHODIMP CMPCVideoDecFilter::SetScanType(MPC_SCAN_TYPE nValue)
{
	CAutoLock cAutoLock(&m_csProps);
	m_nScanType = nValue;
	return S_OK;
}

STDMETHODIMP_(MPC_SCAN_TYPE) CMPCVideoDecFilter::GetScanType()
{
	CAutoLock cAutoLock(&m_csProps);
	return m_nScanType;
}

STDMETHODIMP_(GUID*) CMPCVideoDecFilter::GetDXVADecoderGuid()
{
	return m_pGraph ? (m_pDXVADecoder ? &m_DXVADecoderGUID : (m_hwType == HwType::D3D11 && m_pD3D11Decoder ? m_pD3D11Decoder->GetDecoderGuid() : nullptr)) : nullptr;
}

STDMETHODIMP CMPCVideoDecFilter::SetActiveCodecs(ULONGLONG nValue)
{
	CAutoLock cAutoLock(&m_csProps);
	m_nActiveCodecs = nValue;
	return S_OK;
}

STDMETHODIMP_(ULONGLONG) CMPCVideoDecFilter::GetActiveCodecs()
{
	CAutoLock cAutoLock(&m_csProps);
	return m_nActiveCodecs;
}

STDMETHODIMP CMPCVideoDecFilter::SetARMode(int nValue)
{
	CAutoLock cAutoLock(&m_csProps);
	m_nARMode = nValue;
	return S_OK;
}

STDMETHODIMP_(int) CMPCVideoDecFilter::GetARMode()
{
	CAutoLock cAutoLock(&m_csProps);
	return m_nARMode;
}

STDMETHODIMP CMPCVideoDecFilter::SetHwCodec(MPCHwCodec hwcodec, bool enable)
{
	CAutoLock cAutoLock(&m_csProps);
	if (hwcodec < 0 || hwcodec >= HWCodec_count) {
		return E_INVALIDARG;
	}

	m_bHwCodecs[hwcodec] = enable;
	return S_OK;
}

STDMETHODIMP_(bool) CMPCVideoDecFilter::GetHwCodec(MPCHwCodec hwcodec)
{
	CAutoLock cAutoLock(&m_csProps);

	if (hwcodec < 0 || hwcodec >= HWCodec_count) {
		return false;
	}

	return m_bHwCodecs[hwcodec];
}

STDMETHODIMP CMPCVideoDecFilter::SetHwDecoder(int value)
{
	CAutoLock cAutoLock(&m_csProps);

	if (value < 0 || value >= HWDec_count) {
		return E_INVALIDARG;
	}
	m_nHwDecoder = (MPCHwDecoder)value;
	return S_OK;
}

STDMETHODIMP_(int) CMPCVideoDecFilter::GetHwDecoder()
{
	CAutoLock cAutoLock(&m_csProps);
	return m_nHwDecoder;
}

STDMETHODIMP CMPCVideoDecFilter::SetDXVACheckCompatibility(int nValue)
{
	CAutoLock cAutoLock(&m_csProps);
	m_nDXVACheckCompatibility = nValue;
	return S_OK;
}

STDMETHODIMP_(int) CMPCVideoDecFilter::GetDXVACheckCompatibility()
{
	CAutoLock cAutoLock(&m_csProps);
	return m_nDXVACheckCompatibility;
}

STDMETHODIMP CMPCVideoDecFilter::SetDXVA_SD(int nValue)
{
	CAutoLock cAutoLock(&m_csProps);
	m_nDXVA_SD = nValue;
	return S_OK;
}

STDMETHODIMP_(int) CMPCVideoDecFilter::GetDXVA_SD()
{
	CAutoLock cAutoLock(&m_csProps);
	return m_nDXVA_SD;
}

// === New swscaler options
STDMETHODIMP CMPCVideoDecFilter::SetSwRefresh(int nValue)
{
	CAutoLock cAutoLock(&m_csProps);

	if (nValue &&
			((m_pAVCtx && m_nDecoderMode == MODE_SOFTWARE) || m_pMSDKDecoder)) {
		ChangeOutputMediaFormat(nValue);
	}
	return S_OK;
}

STDMETHODIMP CMPCVideoDecFilter::SetSwPixelFormat(MPCPixelFormat pf, bool enable)
{
	CAutoLock cAutoLock(&m_csProps);
	if (pf < 0 || pf >= PixFmt_count) {
		return E_INVALIDARG;
	}

	m_fPixFmts[pf] = enable;
	return S_OK;
}

STDMETHODIMP_(bool) CMPCVideoDecFilter::GetSwPixelFormat(MPCPixelFormat pf)
{
	CAutoLock cAutoLock(&m_csProps);

	if (pf < 0 || pf >= PixFmt_count) {
		return false;
	}

	return m_fPixFmts[pf];
}

STDMETHODIMP CMPCVideoDecFilter::SetSwConvertToRGB(bool enable)
{
	CAutoLock cAutoLock(&m_csProps);
	m_bSwConvertToRGB = enable;
	return S_OK;
}

STDMETHODIMP_(bool) CMPCVideoDecFilter::GetSwConvertToRGB()
{
	CAutoLock cAutoLock(&m_csProps);
	return m_bSwConvertToRGB;
}

STDMETHODIMP CMPCVideoDecFilter::SetSwRGBLevels(int nValue)
{
	CAutoLock cAutoLock(&m_csProps);
	m_nSwRGBLevels = nValue;
	return S_OK;
}
STDMETHODIMP_(int) CMPCVideoDecFilter::GetSwRGBLevels()
{
	CAutoLock cAutoLock(&m_csProps);
	return m_nSwRGBLevels;
}

STDMETHODIMP_(int) CMPCVideoDecFilter::GetColorSpaceConversion()
{
	CAutoLock cAutoLock(&m_csProps);

	if (!m_pAVCtx) {
		return -1; // no decoder
	}

	if (m_nDecoderMode != MODE_SOFTWARE || m_pAVCtx->pix_fmt == AV_PIX_FMT_NONE || m_FormatConverter.GetOutPixFormat() == PixFmt_None) {
		return -2; // no conversion
	}

	const AVPixFmtDescriptor* av_pfdesc = av_pix_fmt_desc_get(m_pAVCtx->pix_fmt);
	if (!av_pfdesc) {
		return -2;
	}
	bool in_rgb		= !!(av_pfdesc->flags & (AV_PIX_FMT_FLAG_RGB|AV_PIX_FMT_FLAG_PAL));
	bool out_rgb	= (m_FormatConverter.GetOutPixFormat() == PixFmt_RGB32 || m_FormatConverter.GetOutPixFormat() == PixFmt_RGB48);
	if (in_rgb < out_rgb) {
		return 1; // YUV->RGB conversion
	}
	if (in_rgb > out_rgb) {
		return 2; // RGB->YUV conversion
	}

	return 0; // YUV->YUV or RGB->RGB conversion
}

STDMETHODIMP CMPCVideoDecFilter::GetD3D11Adapter(MPC_ADAPTER_ID* pAdapterId)
{
	CAutoLock cLock(&m_csInitDec);
	CheckPointer(pAdapterId, E_FAIL);

	pAdapterId->VendorId = m_HwAdapter.VendorId;
	pAdapterId->DeviceId = m_HwAdapter.DeviceId;

	return S_OK;
}

STDMETHODIMP CMPCVideoDecFilter::SetD3D11Adapter(UINT VendorId, UINT DeviceId)
{
	CAutoLock cLock(&m_csInitDec);

	m_HwAdapter.VendorId = VendorId;
	m_HwAdapter.DeviceId = DeviceId;

	return S_OK;
}

STDMETHODIMP_(CString) CMPCVideoDecFilter::GetInformation(MPCInfo index)
{
	CAutoLock cLock(&m_csInitDec);

	CString infostr;

	switch (index) {
		case INFO_MPCVersion:
			infostr.SetString(MPC_VERSION_WSTR);
			break;
		case INFO_InputFormat:
			if (m_pAVCtx) {
				const auto& pix_fmt = (m_pAVCtx->sw_pix_fmt != AV_PIX_FMT_NONE) ? m_pAVCtx->sw_pix_fmt : m_pAVCtx->pix_fmt;

				infostr = m_pAVCtx->codec_descriptor->name;
				if (m_pAVCtx->codec_id == AV_CODEC_ID_RAWVIDEO) {
					infostr.AppendFormat(L" '%s'", FourccToWStr(m_pAVCtx->codec_tag));
				}
				if (const AVPixFmtDescriptor* desc = av_pix_fmt_desc_get(pix_fmt)) {
					if (desc->flags & AV_PIX_FMT_FLAG_PAL) {
						infostr.Append(L", palettized RGB");
					}
					else if (desc->nb_components == 1 || desc->nb_components == 2) {
						infostr.AppendFormat(L", Gray %d-bit", GetLumaBits(pix_fmt));
					}
					else if(desc->flags & AV_PIX_FMT_FLAG_RGB) {
						int bidepth = 0;
						for (int i = 0; i < desc->nb_components; i++) {
							bidepth += desc->comp[i].depth;
						}
						infostr.Append(desc->flags & AV_PIX_FMT_FLAG_ALPHA ? L", RGBA" : L", RGB");
						infostr.AppendFormat(L" %dbpp", bidepth);
					}
					else if (desc->nb_components == 0) {
						// unknown
					} else {
						infostr.Append(desc->flags & AV_PIX_FMT_FLAG_ALPHA ? L", YUVA" : L", YUV");
						infostr.AppendFormat(L" %d-bit %s", GetLumaBits(pix_fmt), GetChromaSubsamplingStr(pix_fmt));
						if (desc->name && !strncmp(desc->name, "yuvj", 4)) {
							infostr.Append(L" full range");
						}
					}
				}
			} else if (m_pMSDKDecoder) {
				infostr = L"h264(MVC 3D), YUV 8-bit, 4:2:0";
			}
			break;
		case INFO_FrameSize:
			if (m_win && m_hin) {
				__int64 sarx = (__int64)m_arx * m_hin;
				__int64 sary = (__int64)m_ary * m_win;
				ReduceDim(sarx, sary);
				infostr.Format(L"%dx%d, SAR %d:%d, DAR %d:%d", m_win, m_hin, (int)sarx, (int)sary, m_arx, m_ary);
			}
			break;
		case INFO_OutputFormat:
			if (GUID* DxvaGuid = GetDXVADecoderGuid()) {
				infostr.Format(L"%s (%s)", UseDXVA2() ? L"DXVA2" : L"D3D11", GetDXVAModeString(*DxvaGuid));
				break;
			}
			switch (m_hwType) {
				case HwType::D3D11CopyBack: infostr = L"D3D11 Copy-back: "; break;
				case HwType::D3D12CopyBack: infostr = L"D3D12 Copy-back: "; break;
				case HwType::NVDEC:         infostr = L"NVDEC: ";           break;
			}
			if (const SW_OUT_FMT* swof = GetSWOF(m_FormatConverter.GetOutPixFormat())) {
				infostr.AppendFormat(L"%s (%d-bit %s)", swof->name, swof->luma_bits, GetChromaSubsamplingStr(swof->av_pix_fmt));
			}
			break;
		case INFO_GraphicsAdapter:
			infostr = m_strDeviceDescription;
			break;
	}

	return infostr;
}

// IExFilterConfig

STDMETHODIMP CMPCVideoDecFilter::Flt_GetInt(LPCSTR field, int* value)
{
	CheckPointer(value, E_POINTER);

	if (!strcmp(field, "decode_mode")) {
		CAutoLock cLock(&m_csInitDec);

		if (m_nDecoderMode == MODE_SOFTWARE && !m_bUseFFmpeg) {
			*value = MODE_NONE;
		} else {
			*value = m_nDecoderMode;
		}
		return S_OK;
	}

	if (!strcmp(field, "decode_mode_mvc")) {
		// 0 - no, 1 - software decode, 2 - h/w decode
		*value = (m_pMSDKDecoder ? 1 + m_pMSDKDecoder->GetHwAcceleration() : 0);
		return S_OK;
	}

	return E_INVALIDARG;
}

STDMETHODIMP CMPCVideoDecFilter::Flt_GetInt64(LPCSTR field, __int64 *value)
{
	CheckPointer(value, E_POINTER);

	if (!strcmp(field, "version")) {
		*value  = ((uint64_t)MPC_VERSION_MAJOR << 48)
			| ((uint64_t)MPC_VERSION_MINOR << 32)
			| ((uint64_t)MPC_VERSION_PATCH << 16)
			| ((uint64_t)MPC_VERSION_REV);
		return S_OK;
	}

	return E_INVALIDARG;
}

STDMETHODIMP CMPCVideoDecFilter::Flt_GetString(LPCSTR field, LPWSTR* value, unsigned* chars)
{
	// experimental !

	if (!strcmp(field, "input_format")) {
		CStringW ret = GetInformation(INFO_InputFormat);

		int len = ret.GetLength();
		size_t sz = (len + 1) * sizeof(WCHAR);
		LPWSTR buf = (LPWSTR)LocalAlloc(LPTR, sz);

		if (!buf) {
			return E_OUTOFMEMORY;
		}

		wcscpy_s(buf, len + 1, ret);
		*chars = len;
		*value = buf;

		return S_OK;
	}

	if (!strcmp(field, "output_format")) {
		CStringW ret = GetInformation(INFO_OutputFormat);

		int len = ret.GetLength();
		size_t sz = (len + 1) * sizeof(WCHAR);
		LPWSTR buf = (LPWSTR)LocalAlloc(LPTR, sz);

		if (!buf) {
			return E_OUTOFMEMORY;
		}

		wcscpy_s(buf, len + 1, ret);
		*chars = len;
		*value = buf;

		return S_OK;
	}

	return E_INVALIDARG;
}

STDMETHODIMP CMPCVideoDecFilter::Flt_SetBool(LPCSTR field, bool value)
{
	if (strcmp(field, "hw_decoding") == 0) {
		CAutoLock cLock(&m_csInitDec);

		m_bEnableHwDecoding = value;
		return S_OK;
	}

	return E_INVALIDARG;
}

STDMETHODIMP CMPCVideoDecFilter::Flt_SetInt(LPCSTR field, int value)
{
	if (strcmp(field, "mvc_mode") == 0) {
		CAutoLock cLock(&m_csInitDec);

		int nMode = value >> 16;
		bool bSwapLR = (value & 1);

		if (nMode < 0 || nMode > MVC_OUTPUT_TopBottom) {
			return E_INVALIDARG;
		}
		m_iMvcOutputMode = nMode;
		m_bMvcSwapLR = bSwapLR;

		if (m_pMSDKDecoder) {
			m_pMSDKDecoder->SetOutputMode(nMode, bSwapLR);
		}

		return S_OK;
	}

	return E_INVALIDARG;
}

//

HRESULT CMPCVideoDecFilter::CheckDXVA2Decoder(AVCodecContext *c)
{
	CheckPointer(m_pAVCtx, E_POINTER);

	HRESULT hr = S_OK;

	if (m_pDXVADecoder) {
		if ((m_nSurfaceWidth != FFALIGN(c->coded_width, m_nAlign) || m_nSurfaceHeight != FFALIGN(c->coded_height, m_nAlign))
				|| ((m_CodecId == AV_CODEC_ID_HEVC || m_CodecId == AV_CODEC_ID_VP9) && m_dxva_pix_fmt != m_pAVCtx->sw_pix_fmt)) {
			const int depth = GetLumaBits(m_pAVCtx->sw_pix_fmt);
			const bool bHighBitdepth = (depth == 10) && ((m_CodecId == AV_CODEC_ID_HEVC && m_pAVCtx->profile == AV_PROFILE_HEVC_MAIN_10)
														  || (m_CodecId == AV_CODEC_ID_VP9 && m_pAVCtx->profile == AV_PROFILE_VP9_2));

			const bool bBitdepthChanged = (m_bHighBitdepth != bHighBitdepth);

			m_nSurfaceWidth = FFALIGN(c->coded_width, m_nAlign);
			m_nSurfaceHeight = FFALIGN(c->coded_height, m_nAlign);
			m_bHighBitdepth = bHighBitdepth;

			avcodec_flush_buffers(c);
			if (SUCCEEDED(hr = FindDecoderConfiguration())) {
				if (bBitdepthChanged) {
					ChangeOutputMediaFormat(2);
				}
				hr = RecommitAllocator();
			}

			if (FAILED(hr)) {
				m_bFailDXVA2Decode = TRUE;
			} else {
				m_dxva_pix_fmt = m_pAVCtx->sw_pix_fmt;
			}
		}
	}

	return hr;
}

int CMPCVideoDecFilter::av_get_buffer(struct AVCodecContext *c, AVFrame *pic, int flags)
{
	CMPCVideoDecFilter* pFilter = static_cast<CMPCVideoDecFilter*>(c->opaque);
	CheckPointer(pFilter->m_pDXVADecoder, -1);
	if (!pFilter->CheckDXVACompatible(c->codec_id, c->sw_pix_fmt, c->profile)) {
		pFilter->m_bDXVACompatible = false;
		return -1;
	}

	if (FAILED(pFilter->CheckDXVA2Decoder(c))) {
		return -1;
	}

	return pFilter->m_pDXVADecoder->get_buffer_dxva(pic);
}

enum AVPixelFormat CMPCVideoDecFilter::av_get_format(struct AVCodecContext *c, const enum AVPixelFormat * pix_fmts)
{
	CMPCVideoDecFilter* pFilter = static_cast<CMPCVideoDecFilter*>(c->opaque);
	const enum AVPixelFormat *p;
	for (p = pix_fmts; *p != -1; p++) {
		const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(*p);

		if (!desc || !(desc->flags & AV_PIX_FMT_FLAG_HWACCEL))
			break;

		if (*p == AV_PIX_FMT_DXVA2_VLD) {
			if (FAILED(pFilter->CheckDXVA2Decoder(c))) {
				continue;
			}
			break;
		} else if (*p == pFilter->m_HWPixFmt) {
			return *p;
		}
	}

	return *p;
}

// CVideoDecOutputPin

CVideoDecOutputPin::CVideoDecOutputPin(LPCWSTR pObjectName, CBaseVideoFilter* pFilter, HRESULT* phr, LPCWSTR pName)
	: CBaseVideoOutputPin(pObjectName, pFilter, phr, pName)
	, m_pVideoDecFilter(static_cast<CMPCVideoDecFilter*>(pFilter))
{
}

CVideoDecOutputPin::~CVideoDecOutputPin()
{
}

HRESULT CVideoDecOutputPin::InitAllocator(IMemAllocator **ppAlloc)
{
	if (m_pVideoDecFilter && (m_pVideoDecFilter->UseDXVA2() || m_pVideoDecFilter->UseD3D11())) {
		return m_pVideoDecFilter->InitAllocator(ppAlloc);
	}

	return __super::InitAllocator(ppAlloc);
}

namespace MPCVideoDec {
	void GetSupportedFormatList(FORMATS& fmts)
	{
		fmts.clear();

		for (size_t i = 0; i < std::size(sudPinTypesIn); i++) {
			FORMAT fmt = { sudPinTypesIn[i].clsMajorType, ffCodecs[i].clsMinorType, ffCodecs[i].FFMPEGCode};
			fmts.push_back(fmt);
		}
	}
} // namespace MPCVideoDec
